💻 개발/Flutter

[Flutter] SingleChildScrollView, ListView, ListView.bulider

고도고도 2021. 8. 18. 17:36

지난 시간에 Column에 대해 정리했었다. 오늘 정리할 내용은 Column으로 위젯들을 보여줄 때 발생할 수 있는 문제를 해결해주는 또 다른 위젯이다.

import 'package:flutter/material.dart';

void main() {
  return runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("Column 예제"),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          verticalDirection: VerticalDirection.up,
          children: <Widget>[
            Container(
              height: 100,
              color: Colors.red,
            ),
            Container(
              height: 100,
              color: Colors.blue,
            ),
            Container(
              height: 100,
              color: Colors.green,
            ),
            Container(
              height: 100,
              color: Colors.black,
            ),
            Container(
              height: 100,
              color: Colors.indigo,
            ),
            Container(
              height: 100,
              color: Colors.greenAccent,
            ),
            Container(
              height: 100,
              color: Colors.white,
            ),
          ],
        ),
      ),
    );
  }
}

위의 코드를 실행하면 아래와 같은 결과가 나온다.

verticalDirection 에 의해 아래에서 위 방향으로 Container 가 적재(?) 되어 있는 것을 볼 수 있는데 맨 아래에 있는 빨간색 Contiainer가 비정상적 으로 출력된다. 그 이유는 기기의 화면 크기를 벗어났기 때문이다. 빨간색 Container가 표시할 범위는 기기의 MaxHeight를 벗어난 부분이 포함되므로 저렇게 Overflow 가 출력되는 것이다. 그렇다면 어떻게 해결할 수 있을까?

 

방법은 크게 SingleChildScollViewListView 로 두 가지가 존재한다.

SingleChildScrollView

먼저 SinglechildScrollView 를 살펴보자.

SingleChildScrollView SingleChildScrollView({
    Key? key, 
    Axis scrollDirection = Axis.vertical,          
    bool reverse = false,                               
    EdgeInsetsGeometry? padding,             
    bool? primary,                             
    ScrollPhysics? physics,                           
    ScrollController? controller,                    
    Widget? child,                                
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,      
    Clip clipBehavior = Clip.hardEdge,                        
    String? restorationId,                               
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual
    })

역시 다양한 속성들이 존재한다. 지금까지 강의를 들으면서 7개 정도의 어플을 클론코딩 했는데 모든 속성들을 다 사용해본 적은 없다. 위젯의 사용 용도는 위에서 말한 것처럼 스크롤을 통해 화면을 좀 더 유연하게 만들어주는 것이다.

Column의 부모 위젯으로 SingleChildScrollView 를 추가하고 새로 빌드를 해보면 잘려서 보이지 않던 부분까지 정상적으로 출력되는 것을 볼 수 있다. 이 위젯을 이용하면 화면에 스크롤 기능이 추가되어 사용자의 입력에 따라 위-아래 , 혹은 좌-우 로 화면을 움직일 수 있게 된다.

ListView

LIstView 역시 해결할 수 있는 방법 중에 하나이다. 우선 위젯을 살펴보자.

ListView ListView.builder(
    Key? key,            
    Axis scrollDirection = Axis.vertical,           
    bool reverse = false,                       
    ScrollController? controller,                     
    bool? primary,                      
    ScrollPhysics? physics,                    
    bool shrinkWrap = false,                       
    EdgeInsetsGeometry? padding,                   
    double? itemExtent,                   
    required Widget Function(BuildContext, int) itemBuilder,   
    int? itemCount,                      
    bool addAutomaticKeepAlives = true,                    
    bool addRepaintBoundaries = true,                  
    bool addSemanticIndexes = true,             
    double? cacheExtent,                        
    int? semanticChildCount,                       
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,      
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,                     
    String? restorationId,     
    Clip clipBehavior = Clip.hardEdge
    })

엄청나게 많은 속성들이 존재한다. 가장 중요한 핵심 속성은 itemBuilderitemCount 이다. ItemBuilder는 LIstView로 보여줄 위젯들, itemCount는 이 위젯들의 개수를 의미한다.

 

아직 이해가 안갈테니 아래의 코드를 참고해보자.

import 'package:flutter/material.dart';

void main() {
  return runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("ListView 예제"),
        ),
        body: ListView.builder(
          itemBuilder: (context, index) {
            return Container(
              height : 100,
              decoration: BoxDecoration(
                border : Border.all(
                  color : Colors.grey,
                  width: 10,
                ),
              ),
            );
          },
          itemCount: 10,
        )
      ),
    );
  }
}

itemBuilder 는 인자로 contextindex 를 갖는데 context는 쉽게 말해 위젯의 위치, index는 순서라고 생각하면 된다. 사실 context에 대해서도 제대로 집고 넘어가야하는데 우선 위젯의 위치라고 정의하고 다음에 추가적으로 설명하겠다. 이렇게 itemBuilder를 선언하고 itemCount에 표시할 위젯의 개수를 적어준다. 지금은 우선 10이라고 정해줬지만 리스트의 length와 같은 int형 값은 모두 가능하다.

 

위 코드로 빌드를 해보면 아래처럼 결과가 나온다.

역시 스크롤을 통해서 위-아래 로 움직일 수 있다. 그렇다면 ListVIewSingleChildScrollView 는 차이가 없는 것인가? (엄밀히 얘기하자면 ListVIewSingleChildScrollView + Column 을 비교하는게 맞다.) ListView 는 동일한 위젯을 리스트로 보여줄 때(ex. 주소록)에 사용하면 좋고 SingleChildScrollView + Column 의 경우 서로 다른 위젯을 보여주되 위젯들이 화면의 영역을 초과하여 스크롤이 필요할 때 사용하면 된다.

 

하지만 두 개 모두 문제가 있다. 초기에 UI를 렌더링할 때 화면 바깥에 존재하는 아이템까지 모두 렌더링 한다는 것이다. 위에서 작성한 코드의 경우 보여질 위젯의 개수가 적지만 1000개, 10000개의 데이터를 보여준다면 초기 렌더링 시간이 엄청 날 것이다.

 

이를 해결할 수 있는 것이 바로 ListView.builder 다. ListView.builder화면에 보여진 아이템만 렌더링을 진행한다. 최적화 된 ListView 라고 생각하면 된다.

 

관련 자료를 링크해두었다. 참고하면 도움이 될 듯 하다.

 

What is the difference between ListView and ListView.builder and can we use Listview.builder to create and validate and submit f

What is the difference between Listview.builder and Listview? Can we use ListView.builder to submit forms? I am using the Listview.builder now to create forms.

stackoverflow.com

 

What is the difference between ListView and SingleChildScrollView in Flutter?

I have seen few examples using ListView as the main body element of Scaffold and in few tutorials, it's SingleChildScrollView. All that I understood is both allow some axis of scrolling based on the

stackoverflow.com