Flutter 개발일지 day 12
CustomScrollView 도입
어플을 만들 때, 우리는 listview + textformfield 한 줄을 합쳐서 scrollable하게 만들고 싶었다. 입력하는 부분도 listview의 일부분인 것처럼 자연스럽게 만들려고 했다. 그렇기 때문에 listview 자체에서 지원하는 scroll 기능이 아니라, 통째로 scrollable한 새로운 무언가를 찾아야 했다. 그래서 도입한 것이 바로 CustomScrollView이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Expanded(
child: CustomScrollView(
slivers: [
SliverFixedExtentList(
itemExtent: 30,
delegate: SliverChildBuilderDelegate((BuildContext context, int idx){
return Container(
height: 25,
color: Pastel.green,
child: Text("test ${box.get("2022.12.25")![idx]}"), // hive로 box.get(일자)[idx]
);
},
childCount: box.get("2022.12.25") == null ? 0 : box.get("2022.12.25")!.length,
),
),
SliverToBoxAdapter(
child: TextFormField(cursorHeight: 10,),
),
],
),
),
-
Expanded : CustomScrollView는 높이를 주지 않으면 날라간다. 다른 scrollable widget들도 마찬가지지만, 그 자체의 높이를 주지 않으면 얘가 어디까지 무한으로 확장할지 알 수 없기 때문에, 반드시 높이를 줘야한다. 여기서는 Expanded로 to-do appbar 아래서부터 화면 맨 아래까지로 높이를 (자동으로) 주었다.
-
SliverFixedExtentList : CustomScrollView를 쓰면 무조건 하위 widget들로 sliver라는 이름이 붙은 (혹은 관련된) widget들만을 쓸 수 있다. 그래서 ListView의 Sliver 버전인 SLiverFixedExtentList를 썼다.
-
SliverToBoxAdapter : Container의 sliver 버전이다.
intl 라이브러리 추가
DateTime을 써보면 알겠지만, print해보면 년, 월, 일은 물론이고 시간, 분, 초, 그리고 milisec까지.. 일반적인 사용에서는 절대 안쓸법한 자세한 내용을 너무 많이 담고 있다. 따라서 용례에 따라서 적절히 formatting해서 쓰려고 한다. 그러기 위해 intl 라이브러리를 추가했다.
1
2
3
4
//cursor.dart
String returnAsString(){
return DateFormat('yyyy.MM.dd').format(selected);
}
이렇게 쓰면, selected에 들어있는 DateTime 자료형이 2022년 12월 28일을 가르키고 있다면 “2022.12.28” 이라는 string 형태로 리턴해준다. 이는 우리가 hive box에 저장할 key가 된다.
hivebox = {key, value}
key = ‘yyyy.MM.dd’ //string
value =List<ScheduleClass>
따라서 매번 read & write을 할 때마다 returnAsString() 함수를 call해서 key로써 써먹을 수 있다.
customContainer 수정
customContainer는 월, 주차에 표시되는 “일자 한 칸 한 칸을 담는” container이다. 최초로 선언할 때는, 이 customContainer에서 딱히 box를 인자로 가져올 필요가 없었기 때문에 인자로 불러오지 않았는데, 해당 일자에 일정이 존재하는지 아닌지를 표시하기 위해서는 box를 인자로 받아와야만 했다. 따라서 인자를 추가해줬다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//customContainer.dart
class MonthDays extends StatefulWidget{
...
final box;
...
const MonthDays(..., this.box, ) : ... ;
}
class _MonthDaysState extends State<MonthDays> {
...
Widget build(){
return Expanded(
...
if(widget.box.containsKey(DateFormat('yyyy.MM.dd').format(widget.oneDay)))
Positioned(...) // 빨간 원
)
}
}
위에서 widget.box.containsKey를 통해, 이 customContainer가 가르키는 날짜(=oneDay)를 key로 하여 해당 날짜에 일정이 존재하는지 hive box를 검사한다. 존재하는 경우에 일정이 존재한다는 의미로 빨간색 원(점)을 찍어서 표시한다. 위의 코드에서 Positioned(…)이 빨간색 원을 그리는 widget이다. 당연히 없으면 저 widget이 나타나지 않는다.
Slivers 수정
?? 쓰다보니 겹치네.. 커밋하고 나서 동일한 부분의 코드를 그날 바로 수정하기도 했다. 위에서 CustomScrollView를 쓰기 위해서 sliver 관련 widget들을 썼고, 거기에 일단 고정된 값을 아무렇게나 넣어줬었다. 이제 거기서 막 넣은 값들을 바꿔주려고 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
SliverFixedExtentList(
itemExtent: 30,
childCount: box.get(Provider.of<Cursor>(context, listen: false).returnAsString()) == null ?
0 : box.get(Provider.of<Cursor>(context, listen: false).returnAsString())!.length,
delegate: SliverChildBuilderDelegate((BuildContext context, int idx){
return Container(
height: 25,
color: Pastel.green,
child: Text("test ${box.get(Provider.of<Cursor>(context, listen: false).returnAsString())![idx].name}"), // hive로 box.get(일자)[idx]
);
}),
),
SliverToBoxAdapter(
child: TextFormField(
validator: (text){
if(text!.isEmpty){return;}
return null;
},
onSaved: (text){
String scheduleDate = Provider.of<Cursor>(context, listen: false).returnAsString();
if(box.containsKey(scheduleDate)) {
List tmp = box.get(scheduleDate)!;
tmp.add(ScheduleClass(name: text!, date: DateTime.now()));
box.put(scheduleDate, tmp);
}
else {
List tmp = [ScheduleClass(name: text!, date: DateTime.now())];
box.put(scheduleDate, tmp);
}
},
),
),
-
itemExtent : 대충 높이라고 보면된다. listview에서 vertical하게 나열되는 박스들의 높이.
-
childCount: 얘는 List를 받아서 그냥 맘편하게 List들을 나열해주는 그냥 ListView가 아니라 특정 조건에 따라서 Build해주는 ListView.build에 가까운 widget이다. 그렇기 때문에 몇개를 build할 것인지 미리 정해줘야 한다. 당연히 우리는 그날 존재하는 일정 수만큼 build해주고 싶기 때문에, 그에 맞게 지정해줬다. 우리가 참 복잡하게(ㅠㅠ) 코드를 짜놔서, 상세한 설명을 하겠다.
box.get(“key”)는 “key”를 key로 가진 value를 리턴한다. 여기서 value는 scheduleClass의 List이기 때문에,List<ScheduleClass>
라고 보면 된다. 얘가 null이라는건 이 날짜에 일정을 생성한 적이 없기 때문에 list 그 자체가 존재하지 않는 경우이다. 따라서 .length조차 호출할 수 없기 때문에 삼항연산자로 null check를 하고 null이면 0을 리턴, 0이 아니면 list의 길이를 리턴한다. -
delegate : 아래에 어떻게 build될지 정해주는 느낌이다. 위에서 childCount로 몇개 build할지 알려줬기 때문에, 그 개수만큼 build한다. 눈치가 빠르다면, context가 들어있는 걸로 얘가 widget build 해주는 녀석임을 알 수 있다. 여기 아래에 내가 build하고 싶은 widget의 형태를 짜놓으면 그대로 똑같이 반복해서 build 해준다.
-
SliverToBoxAdapter : 저번에는 TextFormField만 넣고 끝냈는데, 이번에는 formfield 안에 여러가지 element를 줬다. validator로 아무것도 안넣은경우 빠꾸시키도록 했다. 그리고 onSaved method에는 위에 FloatingActionButton에서 앞서 구현했던 날짜 삽입을 그대로 넣어줬다. 여기서 주목할 점은, 이번이 최초로 ScheduleClass에 값이 전달되기 시작한다는 것이다. onSaved에서 인자로 받은 text를 그대로 할당해준다. 아직은 “새 일정을 추가”하는 순간에만 “name” 인자 만을 전달하긴 하지만 말이다.
배운점
저번에는 ListView란게 말만 들어보고 뭐하는 놈이지 싶었는데, 오늘 SliverFixedExtentList를 좀 써보면서 원하는 대로 build 해주는 ListView의 편리함을 좀 맛봤다. 아직도 CustomScrollView를 구현하는 방법이 Sliver 시리즈를 쓰는 방법 밖에 없는지는 좀 의문이지만, 뜬금없이 넣은 애들 치고는 우리가 원하는 대로 잘 동작하는 것 같다!