본문 바로가기
flutter/Widget of the Week

Flutter[플러터] / ListView 를 사용하여 스크롤 가능한 리스트 만들기 (목록, controller, 컨트롤러, 상호작용) ListView (Flutter Widget of the Week)

by ch5c 2025. 6. 9.
반응형

ListView class

선형적으로 배열된 위젯의 스크롤 가능한 목록입니다.

ListView 는 가장 일반적으로 사용되는 스크롤 위젯입니다. 스크롤 방향으로 자식 위젯들을 하나씩 표시합니다. 교차 축에서 자식 위젯들은 ListView를 채워야 합니다.

null이 아닌 경우 itemExtent 는 자식이 스크롤 방향으로 지정된 범위를 갖도록 합니다.

null이 아닌 경우, prototypeItem은 스크롤 방향에서 자식 위젯이 주어진 위젯과 동일한 크기를 갖도록 합니다.

itemExtent  prototypeItem을 지정하는 것은 자식이 스스로 범위를 결정하도록 하는 것보다 효율적입니다. 예를 들어 스크롤 위치가 크게 바뀌는 경우, 스크롤 장치가 자식의 범위에 대한 사전 지식을 활용하여 작업을 절약할 수 있기 때문입니다.

itemExtent 와 prototypeItem을 모두 지정할 수 없고 , 둘 중 하나만 지정하거나 아무것도 지정하지 않아야 합니다.

https://youtu.be/KJpkjHGiI5A

공식 문서 코드

 

 

 


앱 제작을 하다 보면 누구나 필연적으로 목록을 만들어야 할 것이다. 그것이 스크롤이 되든 상호작용이 되든 상관없이 목록, 즉 리스트를 만들어야 한다는 것인데 이것을 쉽게 만들 수 있도록 도와주는 위젯이 있다. 바로 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 에 대해서 알아보았다. 좀 이상하게 설명한 부분이 없지 않아 있는 것 같지만 너그럽게 이해해 주면 좋겠다. 도움이 되었길 바라며 마치겠다.

 

반응형