ListView Widget
- ListView는 가장 기본적인 스크롤링 widget이다. ListView는 itemCount만큼의 자식 요소를 가지고, 하나의 자식 밑에 다른 자식이 스크롤 방향으로 쌓이는 형상을 하고 있다.
ListView를 생성하는 4가지 옵션
1. 명시적으로 childeren에 List<Widget>을 넘기는 방법.
- 자식요소의 수가 적은 리스트뷰에 적합하다.
- 눈에 보이는 자식 뿐만 아니라 목록 보기에 표시될 가능성이 있는(즉, 모든 자식 요소)에 대한 작업이 수행된다.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Sample(),
);
}
}
class Sample extends StatelessWidget {
const Sample({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView(
// 자식요소로 List를 바로 넘겨준 것을 확인할 수 있다.
children: [
Text("1번 자식"),
Text("2번 자식"),
Text("3번 자식"),
Text("4번 자식"),
Text("5번 자식"),
Text("6번 자식"),
],
);
}
}
2. ListView.builder를 사용하는 방법.
- 이 생성자는 IndexedWidgetBuilder를 인자로 받는다.
- 실제로 보이는 자식에 대해서만 호출하기 때문에 자식요소가 많은 ListView에 적합하다.
- 자식요소의 Index가 필요한 경우 사용하기 유용하다.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Sample(),
);
}
}
class Sample extends StatelessWidget {
const Sample({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: 100,
// 필수 인자로 itemBuilder를 받아야 하며 이 builder는 context, index를 인자로 가진 함수이다.
itemBuilder: (BuildContext context, int index) {
return Text("$index번 자식요소");
});
}
}
여기서 ListView.builder의 정의를 들어가 보면
ListView.builder({
super.key,
super.scrollDirection,
super.reverse,
super.controller,
super.primary,
super.physics,
super.shrinkWrap,
super.padding,
this.itemExtent,
this.prototypeItem,
required IndexedWidgetBuilder itemBuilder, // <-----------------------
ChildIndexGetter? findChildIndexCallback,
int? itemCount,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
super.cacheExtent,
int? semanticChildCount,
super.dragStartBehavior,
super.keyboardDismissBehavior,
super.restorationId,
super.clipBehavior,
})
이렇게 itemBuilder가 IndexedWidgetBuilder 타입인 것을 확인 할 수 있다.
3. ListView.separted를 사용하는 방법.
- 이 생성자는 아이템과 아이템 사이에 구분자를 넣어줄 수 있다.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Sample(),
);
}
}
class Sample extends StatelessWidget {
const Sample({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.separated(
itemCount: 100,
itemBuilder: (context, index) {
return Text("$index번 자식");
},
separatorBuilder: (context, index) {
return const Divider();
},
);
}
}
예제와 같이 separatorBuilder를 사용하여 item의 요소 사이사이에 내가 원하는 Widget을 넣어줄 수 있다.
4. ListView.custom를 사용하는 방법
- SliverchildDelegate를 가지며, 이를 통해서 자식요소에서의 추가적인 작업을 가능하게 한다.
- 가장 큰 장점은 목록을 출력하기 위한 상태를 분리할 수 있다는 점이다.
- custom과 SliverChildDelegate가 제공하는 속성을 직접 제어하여 ListView의 동작 방식을 새롭게 구현하거나, 최적화를 위한 로직을 추가할 수 있다
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Sample(),
);
}
}
class Sample extends StatefulWidget {
const Sample({Key? key}) : super(key: key);
@override
State<Sample> createState() => _SampleState();
}
class _SampleState extends State<Sample> {
List<String> items = <String>['1', '2', '3', '4', '5'];
void _reverse() {
setState(() {
items = items.reversed.toList();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ListView.custom(
// 다른 생성자들과는 다르게 childrenDelegate를 인자로 사용하여 자식 모델을 제어한다.
childrenDelegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return KeepAlive(
data: items[index],
key: ValueKey<String>(items[index]),
);
},
childCount: items.length,
findChildIndexCallback: (Key key) {
final ValueKey<String> valueKey = key as ValueKey<String>;
final String data = valueKey.value;
return items.indexOf(data);
}),
),
),
bottomNavigationBar: BottomAppBar(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
onPressed: () => _reverse(),
child: const Text('Reverse items'),
),
],
),
),
);
}
}
class KeepAlive extends StatefulWidget {
const KeepAlive({
required Key key,
required this.data,
}) : super(key: key);
final String data;
@override
State<KeepAlive> createState() => _KeepAliveState();
}
class _KeepAliveState extends State<KeepAlive>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return Text(widget.data);
}
}
ListView WIdget 팁
- Null이 아닌 경우 itemExtent 속성은 자식요소가 스크롤 방향으로 지정된 범위만큼 차지하도록 강제한다.
- Null이 아닌 경우 prototypeItem 속성은 스크롤 방향으로 지정된 위젯만큼 차지하도록 강제한다.
- itemExtent나 prototypeItem 속성을 사용하는 것은 자식요소가 자신들이 차지하는 공간의 크기를 결정하는데 효과적이다. 예를 들어 스크롤의 위치가 급격하게 변하였을 때, 자식요소의 범위를 쉽게 계산할 수 있다.
- 빈 리스트 처리 예제
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Empty List Test')),
body: itemCount > 0 // 삼항연산자로 itemCount가 1이상일 경우에만 표현
? ListView.builder(
itemCount: itemCount,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text('Item ${index + 1}'),
);
},
)
: const Center(child: Text('No items')),
);
}
'Flutter' 카테고리의 다른 글
[Flutter] UI(1) - copyWith (1) | 2023.12.07 |
---|---|
[Flutter] Widget(3) - Align (1) | 2023.12.07 |
[Flutter] Widget(1) - Dialog, Alert Dialog, Simple Dialog, Show Dialog (0) | 2023.12.05 |
[flutter] PopupMenuItem 에서 dialog 여는 법 (0) | 2023.10.31 |
[flutter]플러터에서 Intent로 다른 패키지 실행 시 화면 전환이 안되는 현상 (0) | 2023.10.25 |