Flutter ListView가 어떤 위젯이고 사용 방법은 무엇인지 알아보도록 하겠습니다.
기능
ListView는 가장 일반적으로 사용되는 스크롤 위젯으로, 스크롤할 수 있는 영역을 만들고, 지정된 방향(scrollDirection)에 따라 자식들을 차례로 표시합니다. 스크롤 방향, 자식 위젯의 정렬 순서, Named Constructor 등을 통해 다양한 형태의 리스트 생성 옵션을 제공해 줍니다.
사용법
ListView(
children: <Widget>[
Container(
height: 50,
color: Colors.amber[600],
child: const Center(child: Text('Entry A')),
),
Container(
height: 50,
color: Colors.amber[500],
child: const Center(child: Text('Entry B')),
),
Container(
height: 50,
color: Colors.amber[100],
child: const Center(child: Text('Entry C')),
),
],
)
기본적으로 위 코드와 같이 childern 속성에 위젯을 리스트 형태로 넘겨주면 해당 위젯들을 스크롤 방향(수직방향이 Default 값)에 따라 표시해 줍니다. ListView는 위의 기본 생성자 외에 3가지의 Named Constructor를 제공해 줍니다.
Named Constructor란 이름이 있는 생성자란 의미로 클래스에서 기본 생성자 외에 추가적으로 정의 가능한 생성자입니다. 여러 개의 생성자를 만들고 각 생성자마다 파라미터 및 별도의 초기화 작업을 설정하여 다양한 인스턴스를 만드는 방식으로 사용됩니다.
ListView.builder
final List<String> entries = <String>['A', 'B', 'C'];
final List<int> colorCodes = <int>[600, 500, 100];
Widget build(BuildContext context) {
return ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: entries.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
color: Colors.amber[colorCodes[index]],
child: Center(child: Text('Entry ${entries[index]}')),
);
}
);
}
LisrtView.builder를 사용하면 기본 생성자와는 다르게 스크롤 영역에 그려질 자식 위젯을 itemBuilder라는 콜백함수를 통해 넘겨주어야 합니다. 콜백 함수는 itemCount에 지정된 횟수만큼(지정 안 할 시 무제한으로 호출됨!) 반복적으로 호출되며 그때마다 index의 값이 1씩 증가됩니다. 따라서 비슷한 형태의 자식 위젯을 반복적으로 그리는 경우 효과적으로 사용할 수 있습니다. 물론 기본 생성자도 List.generate를 사용하면 이와 비슷한 효과를 낼 수 있습니다.
Widget build(BuildContext context) {
return ListView(
children: List.generate(
100,
(index) => SizedBox(
child: Container(
color: Color(Random().nextInt(0xffffffff)),
child: Center(
child: Text(
index.toString(),
),
),
),
),
),
);
}
그러나 ListView.builder의 경우 실제 자식 위젯이 visible 영역에 노출될 때 Lazy 하게 build 되기 때문에 많은 양의 자식 위젯을 그리거나 무한 스크롤을 구현하는데 더 적합합니다. ( ListView의 기본 생성자는 반대의 이유로 적은 양의 자식 위젯을 그리는데 적합하다고 합니다!)
ListView.separated
final List<String> entries = <String>['A', 'B', 'C'];
final List<int> colorCodes = <int>[600, 500, 100];
Widget build(BuildContext context) {
return ListView.separated(
padding: const EdgeInsets.all(8),
itemCount: entries.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
color: Colors.amber[colorCodes[index]],
child: Center(child: Text('Entry ${entries[index]}')),
);
},
separatorBuilder: (BuildContext context, int index) => const Divider(),
);
}
리스트 형태의 화면을 구현하다 보면 자식 위젯 사이의 구분자를 만들어야 하는 경우가 있습니다. 이때 ListView.separated 생성자를 사용하여 간단하게 자식 위젯 사이에 특정 위젯을 삽입할 수 있습니다.
ListView.custom
ListView.custom은 이해하기 다소 어려운 생성자일 수 있습니다. 최대한 쉽게 풀어서 설명해 보면 말 그대로 List를 화면에 그리는 방식을 custom 하게 만들 수 있는 생성자입니다. 이해가 가지 않으시죠... 아래의 예시 코드를 살펴보겠습니다.
ListView.custom 생성자는 앞에 두 생성자와는 다르게 builder 대신 childrenDelegate라는 SliverChildDelegate 타입의 인스턴스를 받기 때문에 SliverChildDelegate를 상속받은 CustomDelegate를 새로 만들어 넘겨주었습니다.
// main.dart
class Example extends StatelessWidget {
const Example({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.custom(
itemExtent: 100,
childrenDelegate: CustomDelegate(
(context, index) {
return ChildColorWidget(
index: index,
);
},
),
),
);
}
}
그리고 CustomDelegate의 내부 구현은 이와 같습니다. SliverChildDelegate라는 추상 클래스에서 정의한 멤버 변수와 함수를 오버라이드 하여 구현했습니다. 예를 들어, estimatedChildCount getter에서 특정 숫자를 리턴하도록 override 하면 실제 자식의 개 수(itemCount)가 몇 개든 estimatedChildCount에서 리턴한 숫자만큼의 자식 위젯까지만 빌드하기 때문에 그 밑으로는 스크롤이 되지 않습니다. 이처럼 개발자가 직접 클래스를 새로 정의하여 스크롤 영역에 대한 디테일한 설정을 해줄 수 있습니다.
// customDelegate.dart
class CustomDelegate extends SliverChildDelegate {
final NullableIndexedWidgetBuilder builder;
CustomDelegate(this.builder);
@override
Widget? build(BuildContext context, int index) {
return builder(context, index);
}
@override
int? get estimatedChildCount => 20;
@override
double? estimateMaxScrollOffset(int firstIndex, int lastIndex,
double leadingScrollOffset, double trailingScrollOffset) {
return null;
}
@override
bool shouldRebuild(covariant SliverChildDelegate oldDelegate) => false;
}
실제로 앞서 소개한 3가지 생성자도 내부적으로는 SliverChildDelegate를 구현한 클래스를 통해 화면에 자식 위젯을 나타내고 있습니다.
이처럼 ListView.custom 생성자는 'Dlegate패턴을 사용하여 새롭게 정의된 클래스를 사용할 수 있도록 제공되는 생성자이다' 정도로 알고 넘어가면 될 것 같습니다.
Delegate 패턴 (feat. Java 코드)
도입 오늘 평소대로 코딩을 하던 도중 선배와 코드 리뷰를 하던 도중 델리게이트 패턴이라는 얘기가 나왔다. 선배가 델리게이트 패턴에 대해 대략적인 설명도 해주시고 델리게이트 패턴을 적용
week-year.tistory.com
'Flutter&Dart' 카테고리의 다른 글
Flutter 버전관리, FVM을 이용한 SDK 버전 관리 (0) | 2023.01.29 |
---|---|
Flutter InheritedWidget이란, InheritedWidget 사용법 (0) | 2023.01.17 |
Flutter StatelessWidget, StatefulWidget 알아보기 (0) | 2023.01.13 |
[Flutter] 상태(State)에 대해 알아보자, Flutter State (0) | 2023.01.06 |
[Flutter] 실무에서 유용한 플러터 패키지(Pub.dev) (1) | 2022.12.18 |
댓글