ListView class
선형적으로 배열된 위젯의 스크롤 가능한 목록입니다.
ListView 는 가장 일반적으로 사용되는 스크롤 위젯입니다. 스크롤 방향으로 자식 위젯들을 하나씩 표시합니다. 교차 축에서 자식 위젯들은 ListView를 채워야 합니다.
null이 아닌 경우 itemExtent 는 자식이 스크롤 방향으로 지정된 범위를 갖도록 합니다.
null이 아닌 경우, prototypeItem은 스크롤 방향에서 자식 위젯이 주어진 위젯과 동일한 크기를 갖도록 합니다.
itemExtent 나 prototypeItem을 지정하는 것은 자식이 스스로 범위를 결정하도록 하는 것보다 효율적입니다. 예를 들어 스크롤 위치가 크게 바뀌는 경우, 스크롤 장치가 자식의 범위에 대한 사전 지식을 활용하여 작업을 절약할 수 있기 때문입니다.
itemExtent 와 prototypeItem을 모두 지정할 수 없고 , 둘 중 하나만 지정하거나 아무것도 지정하지 않아야 합니다.
공식 문서 코드
앱 제작을 하다 보면 누구나 필연적으로 목록을 만들어야 할 것이다. 그것이 스크롤이 되든 상호작용이 되든 상관없이 목록, 즉 리스트를 만들어야 한다는 것인데 이것을 쉽게 만들 수 있도록 도와주는 위젯이 있다. 바로 ListView 이다.
이 ListView 는 스크롤 가능한 리스트를 만들 때 가장 많이 사용하는 위젯 중 하나인데 다양한 유형의 리스트 UI를 구현할 수 있어서 매우 유용하다. 한번 알아보자.
하위 속성
| 속성명 | 타입 | 기본값 | 설명 |
| scrollDirection | Axis | Axis.vertical | 스크롤 방향을 설정 |
| reverse | bool | false | 스크롤 방향을 반전할지 여부 |
| controller | ScrollController? | null | 스크롤 위치를 제어할 컨트롤러 |
| primary | bool? | null | 주요 스크롤 뷰인지 여부 |
| physics | ScrollPhysics? | null | 스크롤 동작의 물리적 속성을 설정 |
| shrinkWrap | bool | false | 내부 컨텐츠의 크기에 맞게 크기를 축소할지 여부 |
| padding | EdgeInsetsGeometry? | null | 내부 컨텐츠의 패딩 설정 |
| itemExtent | double? | null | 모든 아이템의 높이(또는 너비)를 고정값으로 설정 |
| itemExtentBuilder | ItemExtentBuilder? | null | 각 아이템마다 동적으로 높이(또는 너비)를 반환하는 빌더 |
| prototypeItem | Widget? | null | 기준 위젯을 사용해 모든 아이템의 크기를 동일하게 설정 |
| children | List<Widget> | const <Widget>[] | 리스트에 표시할 자식 위젯 목록 |
| addAutomaticKeepAlives | bool | true | 자동으로 KeepAlive를 추가할지 여부 |
| addRepaintBoundaries | bool | true | 자동으로 RepaintBoundary를 추가할지 여부 |
| addSemanticIndexes | bool | true | 자동으로 Semantic index를 추가할지 여부 |
| cacheExtent | double? | null | 뷰포트 외부에 미리 렌더링할 범위를 설정 |
| semanticChildCount | int? | null | 접근성 시스템에 노출할 자식의 개수 |
| dragStartBehavior | DragStartBehavior | DragStartBehavior.start | 드래그 시작 동작 방식을 설정 |
| keyboardDismissBehavior | ScrollViewKeyboardDismissBehavior | ScrollViewKeyboardDismissBehavior.manual | 스크롤 시 키보드를 자동으로 닫을지 여부 |
| restorationId | String? | null | 상태 복원 시 사용할 ID |
| clipBehavior | Clip | Clip.hardEdge | 리스트가 경계를 벗어나는 내용을 어떻게 처리할지 설정 |
| hitTestBehavior | HitTestBehavior? | null | 히트 테스트 시 터치 이벤트 처리 방식을 설정 |
일단 위의 속성표는 ListView 의 기본 하위 속성들이다. 즉슨 builder 같은 생성자의 하위 속성은 안 붙였다는 말이 되겠는데 그럼에도 엄~청 많다. 하.지.만! ListView 를 사용하기 위해서 알아야 할 것은 이거 하나뿐이다.
ListView(
children: [
// 넣고 싶은 위젯 삽입
],
),
이거 하나만 해줘도 이미 80%는 끝났다.
이제 이 children 안에 위젯들을 Column 이나 Row 에 배치하는 것처럼 배치해 주면 되겠다.
그렇다면 이걸로 스크롤이 되는 위젯이 완성되는가 하면 그건 또 아니다.
SizedBox(
width: 200,
height: 100,
child: ListView(
children: [],
),
),
ListView 의 크기를 기본적으로 잡아줘야 한다. ListView 는 스크롤 가능한 위젯, 즉 사이즈를 무제한으로 가지는 위젯이기 때문에 바로 사용할 시 오류가 날 확률이 높다. (사실 ListView 를 자체로 사용할 땐 거의 나지 않는다.)
그럼 이제 좀 더 근본적인 의문이 들 수 있다. children 안에 위젯을 배치하면 어떻게 보이는가? 그것에 대한 해답은 이러하다.
현재 코드에서는 너비 200, 높이 100이라는 값으로 ListView 의 크기를 지정해주고 있다. 이제 이 뜻은 ListView 의 children 안에 있는 자식의 크기가 저 값을 넘어가면 화면, ListView 로 부터 잘린다는 것인데 알아야 할 점은 이것은 스크롤되는 위젯이란 것이다. 그러므로 잘린 부분으로 스크롤을 하면 정상적으로 잘린 부분이 화면에 표시가 될 것이다. (그것이 스크롤이니까.)
암튼 이렇게 리스트뷰 자체는 간단하게 구현 가능하다. 이제 여기서 살짝 세부로 들어가면
scrollDirection: Axis.horizontal,
이 값을 사용하여 기본 값인 세로 스크롤 방향에서 가로 스크롤로 전환시킬 수 있다. 또한 기본적으로 controller 도 지원을 하기 때문에 연결해야 할 컨트롤러를 넣어줄 수 있다.
controller: ScrollController(), // 당연하지만 이따구로 사용하면 절대 X
사실 이제부터가 핵심이다. ListView 를 사용하는 사람들은 이 위젯 자체로 사용하지 않고 생성자를 붙여서 사용하는데 이제 그것에 대해 한번 알아보자.
ListView.builder
리스트뷰의 존재 유무라고 불려도 될 정도의 알파이자 오메가인 생성자이다.
이 생성자는 ListView 를 builder 로 바꿔주는 생성자이다.
ListView.builder(itemBuilder: (context, index) {
return Container();
},),
기본적으로 불러왔을 때의 코드는 이런 식이다. 빌더 함수 안에서는 context 와 index 를 전달해 주는데 여기서 핵심은 바로 index 인자 값이다. 이 활용법에 대해서 예제 코드를 보며 알아보자.
(전체코드)
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: Test()));
class Test extends StatefulWidget {
const Test({super.key});
@override
State<Test> createState() => _TestState();
}
class _TestState extends State<Test> {
List items = ['A', 'B', 'C', 'D', 'E'];
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SizedBox(
width: 200,
height: 200,
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return SizedBox(
height: 75,
child: Text(items[index]),
);
},),
),
),
);
}
}
일단 ListView.builder 를 사용하기 위해선 itemCount 값을 지정해줘야 한다. 필수 속성은 아니다면 지정해주지 않으면 무한한 자식이 생겨버려서 심각한 오류를 발생시킬 수 있다.
itemCount: 15, // 자신이 만들 아이템의 수
이 코드에서는 미리 만들어 놓은 items 의 length, 즉 길이(int) 값을 뽑아와서 itemCount 에 넣어 줬다.
List items = ['A', 'B', 'C', 'D', 'E'];
보시다시피 items 의 아이템 수는 A, B, C, D, E 총 5개인데 그러므로 현재 코드에서 itemCount 에 들어간 실질적인 수는 5가 되겠다.
그리고 itemBuilder, 이것은 필수 하위 속성인데 빌더 함수를 넣어주면 된다. 앞서 말했듯이 여기서 주목해야 하는 것은 index 인데 봐라, 현재 코드에서 index 를 쓰고 있는 곳이 있다. 바로 넣어놓은 텍스트에서이다.
Text(items[index])
다들 List, 배열에 대해서는 알고 있을 것이다. dart 에서도 알고 있는 것 그대로 따라가기 때문에 어렵지 않은데 먼저 items 라는 변수를 Text 안에서 불러왔다. items 는 List 타입을 가지고 있으니 당연하게도 그냥 사용하게 된다면 오류가 난다.
하지만 items 의 안에는 A, B, C 즉 String 값이 들어있어 Text 안에서 사용할 수 있는데 그 String 을 뽑아오는 코드가 바로 이 코드인 것이다.
빌더 함수에서 주는 index 의 역할은 현재 위젯의 index 를 제공하는 것인데 이 말은 즉슨 현재 코드에서 만들어져 있는 5개의 아이템, 즉 5개의 위젯은 각각 고유의 index 를 가지고 있다. 첫번째 위젯에 할당되어 있는 index 는 0, 마지막 위젯에 할당되어 있는 index 는 4 되시겠다. 여기서 이제 감이 오는가?
items[index] 이 코드는 items[0 ~ 4] 다 가져오겠다는 의미인 것이다. index 는 각각 번호에 맞게 index 를 부여하고 그 index 를 활용하여 리스트에서 index 에 맞는 값을 뽑아오는 것이다. 이게 다다.
단지 이제 정보가 복잡해지고 그런 것이지 결국에 ListView.builder 가 해주는 일은 index 를 전달해 주는 일. 그것이 끝인 것이다.
그렇다면 당연하게도 클릭을 해야 하는 위젯이 있을 때도
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return SizedBox(
height: 75,
child: GestureDetector(
onTap: () {
print(index); // 결과 값: 첫번 째 위젯 클릭시 0 출력
},
child: Text(items[index]),
),
);
},
),
클릭한 위젯의 index 를 받아 올 수 있기 때문에 세밀한 조절에도 쓰일 수 있는 것이다.
암튼 이렇게 ListView 에 대해서 알아보았다. 좀 이상하게 설명한 부분이 없지 않아 있는 것 같지만 너그럽게 이해해 주면 좋겠다. 도움이 되었길 바라며 마치겠다.