[Flutter] List, ListView, ListView.Builder 차이

2023. 3. 11. 19:38Developers 공간 [Shorts]/Frontend

728x90
반응형
<분류>
A. 수단
- OS/Platform/Tool : Linux, Kubernetes(k8s), Docker, AWS
- Package Manager : node.js, yarn, brew, 
- Compiler/Transpillar : React, Nvcc, gcc/g++, Babel, Flutter

- Module Bundler  : React, Webpack, Parcel

B. 언어
- C/C++, python, Javacsript, Typescript, Go-Lang, CUDA, Dart, HTML/CSS

C. 라이브러리 및 프레임워크 및 SDK
- OpenCV, OpenCL, FastAPI, PyTorch, Tensorflow, Nsight

 


1. What? (현상)

List와 관련된 다양한 언어가 있어. 정확히 구분해 설명해놓으려고 합니다.

 

+ 그리고 추가로 ListView와 같은 "리스트 렌더링" 안에 "리스트 렌더링"을 추가로 넣는 경우 아래와 같은 에러가 나는데, 왜그런지와 어떻게 해결할 수 있을지 살펴봅니다.

"RenderBox was not laid out: RenderViewport#123ab NEEDS-LAYOUT NEEDS-PAINT"

2. Why? (원인)

  • + 추가 에러의 경우 리스트 안의 리스트의 크기가 무한하기 때문에 일어나는 에러라고 볼 수 있습니다. 즉, 밖의 사이즈는 안의 리스트를 기준으로 만들어야하는데, 밖에서는 안의 개수를 알 수 없고 안의 리스트는 밖의 사이즈를 알수 없기 때문에 나는 에러입니다.

3. How? (해결책)

  • List<>() : 자료구조로, 단순히 리스트를 의미합니다. 아직 위젯이 아니므로, 아래와 같이 위젯리스트로 만들고 직접 렌더링 해주어야합니다.
    • ** Tuple List : 'package:tuple/tuple.dart' 패키지를 활용하면 아래와 같이 구현 가능합니다.
      List<Tuple2<String,Widget>> tabLists = [Tuple2('tab1', Template()), Tuple2('tab2', Template2())];
       --> tabLists[i].item1
// Decaration1
List<Widget> widgets = [
    Text('Toyota', style: kStyle,),
    Text('VolksWagen', style: kStyle,),
    Text('Nissan', style: kStyle,),
    Text('Renault', style: kStyle,),
    Text('Mercedes', style: kStyle,),
    Text('BMW', style: kStyle,)
  ];
  
// Decaration2
List<Widget> _buildUserGroups(BuildContext context) {
    var userGroup = List<Widget>();
    userGroup.add(Text("Users"));

    for (var i = 0; i < 5; i++) {
    	userGroup.add(Text("User " + i.toString()));
    }

    return userGroup;
}

// Widget Rendering
Column(
	children:  _buildUserGroups(context).map((Widget item)=> item),
)
  • ListView() : ListView는 위젯으로, Rendering하는데 사용합니다. 가장 기본적인 Default Constructor로, 직접 children:[] 에 Widget들을 넣어 scroll 가능한 위젯을 만들어줍니다.
    • + 추가 에러의 경우 ListView>ListView 혹은 Column >ListView 대신 아래와 같은 옵션을 사용할 수 있습니다.
      • Column > ListView(shrinkWrap:true)
      • Column > [Expanded/Flexible]
      • Column > [SizedBox]
      • CustomScrollView>SliverList
    • + 추가 에러의 경우 아래 파라미터에 대한 간단한 소개를 알면 조금 더 이해할 수 있습니다.(아래 더보기 클릭)
더보기

-----------------------------------------------------------------------------------------------------

<ListView 파라미터 간단히 소개>

  • scrollDirection : Axis.vertical 혹은 Axis.horizontal을 사용가능합니다.
  • children : 직접 만들어 넣어주어도되지만, ListTile()을 사용하면 width를 정해주지않아도 Expanded()처럼 알아서 화면을 최대한 활용하기 때문에 좋습니다.(vertical 일 때)
    ** horizontal 의 경우 정해진 width로 감싸주는 것이 좋습니다.
  • reverse : 역순도 가능합니다.
  • controller : 보통은 member 변수로 있지만, 따로 scrollController를 선언해 넣어주면 나중에 scrollController.animateTo()를 활용해 ListView의 위치를 이동시키는데 사용할 수도 있습니다.
ScrollController _scrollController = _scrollController = new ScrollController();

...

child :	ListView(
            scrollDirection: Axis.vertical,
            shrinkWrap: true,
            controller: _scrollController,
            children: _items.map((Item item) {
	            return _singleItemDisplay(item);
		}).toList());
        
viewModel.scrollController.animateTo(viewModel.scrollController.position.extentInside, duration: Duration(seconds: 2), curve: Curves.ease);
  • shrinkWrap : ListView안에 또 ListView를 넣는 경우, child의 "크기만큼만 할당"하게 하기 위해서 child ListView에 true를 해줍니다. 즉, 사용자에게 보이는 외부 높이가 아닌, 내부적인 높이(자식의 위젯 수에 따라 무한히 늘어나는)를 활용해 렌더링 하는 것이다.(굉장히 cost-expensive합니다)
    ** slivers : 애니메이션 효과로, 스크롤시 위로 올라가 사라지는 등 헤더의 하단부를 보여주는 효과를 의미합니다. 위의 경우, ListView>ListView 대신 "CustomScrollView>SliverList"를 활용하면 문제를 해결할 수 있습니다.
  • physics : 위 shrinkWrap==true 상황에, child의 physics에 NeverScrollableScrollPhysics()를 넣어주어 child의 scroll을 금지시켜줄 수 있습니다. 이외에 BouncingScrollPhysics() 등 스크롤 시의 효과를 선택해줄 수 있습니다.

-----------------------------------------------------------------------------------------------------

ListView(
  padding: const EdgeInsets.all(8),
  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')),
    ),
  ],
)
  • ListView().builder() : ListView의 named constructor로, on demand로 각각의 위젯들이 만들어질 것이므로, itemCount:itemBuilder: 를 활용해 렌더링할 위젯을 만들어냅니다. 리스트가 너무 길거나, pagenation을 구현할 때 사용합니다.
    ** Pagenation(페이칭 처리) : 매번 전부 가져오면 너무 느려지므로, 데이터를 조금씩 나눠 가져오고 user 의 request에 의해 가져오게 하는 방식입니다. 이는 보통 Front와 Back 모두의 구현이 필요합니다.
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]}')),
      );
    }
  );
}
  • ListView().separate() : ListView의 naed constructor로, separator로 구분되는 ListView를 만들어 냅니다. 위와 같이 itemCount:itemBuilder: 를 활용해 렌더링할 위젯을 만들어내지만, separatorBuilder로 구분선까지 만들어주는 것이 특이합니다.
final List<String> entries = <String>['A', 'B', 'C'];
final List<int> colorCodes = <int>[600, 500, 100];

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(),
);

 

 

이외에도 GridView, PageView 등도 위와 같으니, api 페이지를 참고해 위와 같이 다양한 constructor를 활용해 구현하면 좋습니다.


https://terry1213.github.io/flutter/flutter-decoding-flutter-shrinkwrap-vs-slivers/

https://api.flutter.dev/flutter/widgets/ListView-class.html

 

728x90
반응형