3. 영화 선택 페이지
3-2. 영화 선택 페이지 - 기능 별 코드 설명
3-2-8. progressBar로 영화 선택 바 구현하기
Widget progressBar(BuildContext context, List<String> selectedMovies) {
return Padding(
padding: const EdgeInsets.all(15),
child: Stack(
alignment: Alignment.center,
children: [
LinearProgressIndicator(
value: selectedMovies.length / 10,
backgroundColor: Colors.grey[300],
color: mainColor,
minHeight: 10,
),
Positioned.fill(
child: CustomPaint(
painter: GridPainter(),
),
),
],
),
);
}
- 선택한 영화의 개수에 따라 진행 상황을 시각적으로 보여주는 ProgressBar를 위젯으로 구현했다.
- Stack: 위젯 겹치기.
- 앞에서 한번 써봤다. 여러 위젯을 겹처서 표시하는 위젯이다.
- LinearProgressIndicator, Positioned.fill 위젯을 stack 으로 쌓아서 선택된 영화 갯수만큼 bar가 채워지도록 만들었다.
- LinearProgressIndicator(): 진행 bar를 표시하는 futter 기본 위젯.
- 사용자가 선택한 영화의 개수는 selectedMovies.length로 측정되며, selectedMovies.length / 10의 계산을 통해 진행률로 계산되도록 했다.
- 따라서 최대 10개의 영화를 기준으로 진행 바가 표시되고, 사용자가 10개의 영화를 모두 선택하면 progressBar가 완전히 채워진다.
- backgroundColor는 진행되지 않은 부분을 표시하는 회색(Colors.grey[300])으로 설정되었으며, selectedMovies 갯수만큼 영화 선택이 진행된 부분인 color는 mainColor로 바뀐다.
- Positioned.fill(): 특정 위젯으로 채우기.
- Stack 내에서 자식 위젯이 부모의 전체 크기를 채우게 만드는 역할을 한다.
- 여기서는 격자를 그려줄 CustomPaint 위젯이 진행 바 전체에 걸쳐 표시된다.
이렇게 영화 선택 과정에서 사용자가 현재 얼마나 많은 영화를 선택했는지를 실시간으로 보여주고. 사용자가 영화를 하나씩 선택할 때마다 진행 바가 점차적으로 채워지도록 만들었다.
3-2-9. GridPainter 클래스로 수직선과 숫자 표시
- GridPainter 클래스를 이용하여 CustomPainter를 상속받아 Canvas에 직접 그리드와 텍스트를 그리는 기능을 구현했다.
- Flutter에서 CustomPainter는 사용자 정의 그래픽을 그릴 수 있도록 제공되는 기능이다. 일반적인 위젯들로 표현하기 어려운 도형, 라인, 텍스트 등을 Canvas 위에 직접 그릴 수 있다.
- 여기서는 수직선과 각 선에 해당하는 숫자 텍스트를 표시한다.
class GridPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.black
..strokeWidth = 2;
final textPainter = TextPainter(
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
);
for (int i = 0; i < 10; i++) {
double x = size.width * (i + 1) / 10;
canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint);
textPainter.text = TextSpan(
text: '${i + 1}',
style: const TextStyle(color: Colors.black, fontSize: 12),
);
textPainter.layout();
textPainter.paint(canvas, Offset(x - size.width / 20 - textPainter.width / 2, size.height / 2 - textPainter.height / 2));
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
- Canvas: 실제 그림을 그리는 도화지.
- paint 메서드의 인수로 전달되며, 이곳에 직접 선을 긋거나 도형, 텍스트 등을 그릴 수 있다.
- paint 메서드: Canvas에 그리기 작업을 수행하는 핵심 부분.
- Paint(): 선을 그릴 때 사용할 색상과 두께를 설정.
- color는 검정색(Colors.black)으로 설정한다.
- strokeWidth는 선의 두께로, 적당한 굵기인 2로 설정한다.
- TextPainter(): Canvas에 텍스트를 그리는 역할.
- textAlign은 텍스트의 정렬을 가운데로 설정한다.
- textDirection은 텍스트의 방향을 좌에서 우(ltr)로 설정하여, 기본적인 텍스트 출력 방향을 지정한다.
- for문을 통한 반복적인 그리기 작업.
- for (int i = 0; i < 10; i++) 반복문은 10번 반복되며, 화면에 수직선과 숫자를 그린다.
- double x = size.width * (i + 1) / 10;를 이용해 각 구간의 x 좌표를 계산한다. 이렇게 하면 진행 바의 전체 너비를 10등분하여 각 선의 위치를 정할 수 있다.
- 수직선 그리기: canvas.drawLine()을 통해 각 수직선의 시작 좌표와 끝 좌표를 지정하여 선을 그린다. 각 수직선은 화면 너비를 10등분한 위치에 그려진다.
- 숫자 텍스트 그리기: 각 수직선 아래에 1~10까지의 숫자를 표시한다. TextSpan을 사용하여 텍스트 스타일을 정의한 후, textPainter.layout()을 호출해 텍스트 크기를 계산하고, 적절한 위치에 배치하여 textPainter.paint()로 그린다.
- 텍스트 위치 조정: 텍스트의 위치는 각 선의 중앙에 오도록 Offset을 사용해 조정되었다. 이를 통해 텍스트가 선에 정확히 맞게 배치될 수 있도록 했다.
- Paint(): 선을 그릴 때 사용할 색상과 두께를 설정.
- shouldRepaint 메서드: Canvas를 다시 그릴 필요가 있는지 여부를 결정.
- 여기서는 false로 설정하여, 이미 그려진 내용은 재사용하고, 다시 그릴 필요가 없음을 명시했다.
3-3. 영화 선택 페이지 - 전체 코드
import 'package:flutter/material.dart';
import 'package:movie_rec/pages/select_score_page.dart';
class SelectMoviePage extends StatefulWidget {
const SelectMoviePage({super.key});
@override
State<SelectMoviePage> createState() => _SelectMoviePageState();
}
Color mainColor = Colors.amber;
class _SelectMoviePageState extends State<SelectMoviePage> {
List<String> movies = List.generate(12, (index) => '영화 $index');
List<String> selectedMovies = [];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: appBar(),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.all(15),
child: Text(
'재미있게 본 영화를 선택해주세요.(10개)',
),
),
progressBar(context, selectedMovies),
selectMovieTile(),
],
),
bottomNavigationBar: bottomAppBarButton(context),
);
}
AppBar appBar() {
return AppBar(
backgroundColor: mainColor,
title: const Text('영화 추천 앱'),
);
}
void toggleSelection(String movie) {
setState(() {
print('movie: ' + movie.toString());
if (selectedMovies.contains(movie)) {
// 만약 타일이 선택되어 있는 상태라면
selectedMovies.remove(movie); // selectedMovies 리스트에서 제거
} else {
//만약 타일이 선택되어 있지 않은 상태라면
if (selectedMovies.length < 10) {
//그리고 선택된 영화가 아직 10개가 안되었다면
selectedMovies.add(movie); // selectedMovies 리스트에 포함
}
// else { // 10개가 다 찼다면
// (별다른 Action을 정의하지 않았기 때문에) selectedMovies 리스트에는 변화가 없다
// }
}
print('selectedMovies: ' + selectedMovies.toString());
});
}
Widget bottomAppBarButton(BuildContext context) {
return BottomAppBar(
child: ElevatedButton(
onPressed: selectedMovies.length == 10
? () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SelectScorePage(selectedMovies: selectedMovies),
),
);
}
: null,
style: ElevatedButton.styleFrom(
textStyle: const TextStyle(fontSize: 20),
),
child: selectedMovies.length == 10
? Text(
'다음',
style: TextStyle(color: mainColor),
)
: const Text('다음'),
),
);
}
Widget selectMovieTile() {
// GridView는 스크롤이 가능하기 때문에 그 상위에서 스크롤 범위를 정해줘야 함
// 스크롤 가능 범위를 최대한 확장시켜서 사용하기 위해 Expanded를 사용
return Expanded(
child: GridView.builder(
padding: EdgeInsets.zero,
itemCount: movies.length, // 영화 갯수만큼 표시(현재는 List로 만든 movies변수 길이인 12개)
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // 가로 타일 갯수
crossAxisSpacing: 1, // 가로 타일 간 간격
mainAxisSpacing: 1, // 세로 타일 간 간격
childAspectRatio: 1 / 1.5, // 타일의 가로세로 비율
),
itemBuilder: (context, index) {
// movies 수 만큼 index로 하나씩 출력(0~11)
String movie = movies[index]; // movies 하나를 movie 변수에 할당
// selectedMovies변수는 선택된 영화를 담기위에 앞서 정의한 변수로,
// 현재의 movie가 selectedMovies에 포함되어 있으면 선택된 것이기 때문에 isSelected = True,
// 포함되어 있지 않으면 선택되지 않은 것이기 때문에 isSelected = False가 됨
bool isSelected = selectedMovies.contains(movie);
// 타일 하나를 어떻게 표시할건지 정의
return Padding(
padding: const EdgeInsets.all(3.0),
child: InkWell(
onTap: () => toggleSelection(movie),
child: Stack(
children: [
Container(
// 위 코드와 동일한 부분
width: double.infinity,
height: 300,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: isSelected ? mainColor.withOpacity(0.7) : Colors.grey,
),
),
if (isSelected)
const Center(
child: Icon(
Icons.check,
color: Colors.white,
size: 50,
),
),
Container(
width: double.infinity,
height: 300,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
gradient: const LinearGradient(
colors: [Colors.transparent, Colors.black87],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
movie,
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
);
},
),
);
}
}
Widget progressBar(BuildContext context, List<String> selectedMovies) {
return Padding(
padding: const EdgeInsets.all(15),
child: Stack(
alignment: Alignment.center,
children: [
LinearProgressIndicator(
value: selectedMovies.length / 10,
backgroundColor: Colors.grey[300],
color: mainColor,
minHeight: 10,
),
Positioned.fill(
child: CustomPaint(
painter: GridPainter(),
),
),
],
),
);
}
class GridPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.black
..strokeWidth = 2;
final textPainter = TextPainter(
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
);
for (int i = 0; i < 10; i++) {
double x = size.width * (i + 1) / 10;
canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint);
textPainter.text = TextSpan(
text: '${i + 1}',
style: const TextStyle(color: Colors.black, fontSize: 12),
);
textPainter.layout();
textPainter.paint(canvas, Offset(x - size.width / 20 - textPainter.width / 2, size.height / 2 - textPainter.height / 2));
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
- 이렇게 영화 선택 페이지에 대해 알아보았다.
- 다음 글에서는 영화 점수 페이지에 대해 알아보자.
'추천시스템 앱 개발 > 프론트엔드(Flutter)' 카테고리의 다른 글
[Flutter/플러터] 영화추천 화면 만들기 -영화 선택 페이지 2 (2) | 2024.10.13 |
---|---|
[Flutter/플러터] 영화추천 화면 만들기 -영화 선택 페이지 1 (0) | 2024.10.12 |
[Flutter/플러터] 영화추천 화면 만들기 - main.dart (4) | 2024.10.09 |
[Flutter/플러터] 새 프로젝트 만들기 - 폴더명 변경 (4) | 2024.10.07 |
[Flutter/플러터] 새 프로젝트 만들기 (0) | 2024.06.16 |
댓글