사이드 프로젝트 냉장고지도를 진행하던중 아래와같이 구현하고싶었다.
여기서 장보기추가버튼은 floatingActionButton으로 구현할 생각이었고 이것저것 시도하던 중 SingleChildScrollView 안에 ListView를 쓰는것은 가능할까? 라는 생각이 들어 테스트를 시작했다. 결론은 가능하다. Listview의 shrinkWrap 속성을 true로, primary 속성을 false로 주면 된다. shrinkWrap은 ListView의 크기를 고정하는 것이고, primary는 리스트의 스크롤을 막는 것이다.
테스트 하던중 itemCount를 주지않아 무한대로 생성하게했더니 앱이 죽어버렸다. 왜인지 생각해보니 화면에 계속 객체를 끝없이 그리고있어 화면 무한로딩이 발생했기 때문이다.
여기서 또 질문이 생기는데 그렇다면 ListView는 어떻게 처리하길래 무한스크롤이 가능한 것일까?
지금 찾아보니 ListView는 모든 데이터를 다 그려주는데 ListView.builder는 필요한 부분만 그려준다고 한다. 사용자가 스크롤을 내릴 때 itemBuilder가 실행되어 빈 공간에 리스트를 그려준다고 한다. ListView의 itemCount 부분은 itemBuilder를 몇번 실행할지 정해주는 것이라고 한다. 만약 itemCount 속성을 사용하지 않을경우 공간에 가득 찰때까지 itemBuilder를 실행해준다. 즉, 사용자가 스크롤을 내릴때 이 작업이 수행되는 것이다.
위 내용을 보고 생각했을때 원래 ListView는 지정된 공간안에서 사용하도록 설계되어있다고 생각한다(그래서 위치를 지정하지않으면 화면에 보이지조차 않는다..).
SingleChildScrollView 안에서 ListView가 작동하다보니 SingleChildScrollView는 내용물이 생성될때마다 아래로 끊임없이 생성해주는 것 같다. ListView는 부모의 크기를 따르므로 크기가 정해지지않아 계속 생성되는 무한루프에 빠지지 않아서 앱이 멈춘것처럼 보이는게 아니었을까? 싶다.(혹시나 정확한 내용을 알고계시다면 댓글부탁드립니다 ^^)
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: [
CustomCalendar(widgetName: context.widget.toString()),
ToggleButtons(
onPressed: (index) {
setState(
() {
for (int i = 0; i < _isSelected.length; i++) {
_isSelected[i] = i == index;
}
},
);
},
fillColor: Colors.black,
isSelected: _isSelected,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"장보기 목록",
style: TextStyle(
fontSize: 17,
color: _isSelected[0] == true
? ColorList.white
: ColorList.black,
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"장보기 완료",
style: TextStyle(
fontSize: 17,
color: _isSelected[1] == true
? ColorList.white
: ColorList.black,
),
),
),
],
),
// 장보기 item
ListView.builder(
shrinkWrap: true,
primary: false,
itemCount: 10,
itemBuilder: (context, index) {
return ExpansionTile(
title: Row(
children: [
Text("장보기 목록"),
Text("2023년 7월 12일"),
],
));
}),
],
),
),
),
);
}
}