ListView로 리스트를 만들 때 정렬 기능을 추가하고 싶을 때가 있지 않나요?
ReorderableListView는 사용자가 리스트 항목을 드래그 앤 드롭하여
정렬할 수 있는 Flutter의 위젯입니다.
이번에는 ReorderableListView와 설정할 수 있는 속성에 대해 소개합니다.
- 기본적인 ReorderableListView의 사용법
- ReorderableListView의 속성
- itemBuilder(필수)
- itemCount(필수)
- onReorder(필수)
- itemExtent(옵션)
- prototypeItem(옵션)
- proxyDecorator(옵션)
- buildDefaultDragHandles(옵션)
- padding(옵션)
- header(옵션)
- footer(옵션)
- scrollDirection(옵션)
- reverse(옵션)
- scrollController(옵션)
- primary(옵션)
- physics(옵션)
- shrinkWrap(옵션)
- anchor(옵션)
- cacheExtent(옵션)
- dragStartBehavior(옵션)
- keyboardDismissBehavior(옵션)
- restorationId(옵션)
- clipBehavior(옵션)
- 요약
기본적인 ReorderableListView의 사용법
아래에 기본적인 ReorderableListView의 구현 코드를 보여드립니다.
아래 예제에서는 리스트에 포함된 항목을 드래그하여 순서를 변경할 수 있습니다.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'ReorderableListView Example',
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
State createState() => _HomePageState();
}
class _HomePageState extends State {
final List _items = List.generate(20, (index) => "Item $index");
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('ReorderableListView Example'),
),
body: ReorderableListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) {
return ListTile(
key: ValueKey(_items[index]),
title: Text(_items[index]),
trailing: const Icon(Icons.drag_handle),
);
},
onReorder: (oldIndex, newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final item = _items.removeAt(oldIndex);
_items.insert(newIndex, item);
});
},
),
);
}
}
ReorderableListView.builder를 사용하여 20개의 리스트 아이템을 표시하고 있습니다.
ReorderableListView의 속성
ReorderableListView에는 리스트를 커스터마이징하기 위한 많은 속성이 있습니다.
사용 가능한 속성은 ListView와 비슷하지만, 약간 다릅니다.
itemBuilder(필수)
itemBuilder는 각 항목을 생성하는 속성입니다.
받은 리스트 항목을 하나씩 처리하여 표시되는 위젯을 반환합니다.
itemCount: _items.length,
itemBuilder: (context, index) {
return ListTile(
key: ValueKey(_items[index]),
title: Text(_items[index]),
trailing: const Icon(Icons.drag_handle),
);
},
itemCount(필수)
itemCount는 리스트의 항목 수를 지정하는 속성입니다.
ReorderableListView에서는 itemCount 속성이 필수입니다.
itemCount: _items.length,
onReorder(필수)
리스트 항목이 재배치되었을 때 호출되는 콜백 함수입니다.
onReorder는 사용자가 드롭했을 때 실행됩니다.
다음은 기본적인 onReorder의 처리지만, 설명하기 쉽게 코드를 재배치했습니다.
onReorder: (oldIndex, newIndex) {
setState(() {
// 리스트에서 oldIndex의 Item을 삭제&취득합니다
final item = _items.removeAt(oldIndex);
// 그러면 oldIndex 이후의 Index가 1씩 밀리기 때문에,
// oldIndex보다 newIndex가 클 경우 newIndex에서 -1 해야 합니다
if (newIndex > oldIndex) {
newIndex -= 1;
}
// 아까 삭제한 Item을 newIndex에 삽입합니다
_items.insert(newIndex, item);
});
},
itemExtent(옵션)
각 항목의 고정 높이(세로 길이)를 지정할 수 있습니다.
설정하면 각 항목의 높이가 통일되어 스크롤 성능이 향상됩니다. 기본값은 null입니다.
itemExtent: 30.0,
prototypeItem(옵션)
리스트의 레이아웃이 제공한 위젯에 따라 계산됩니다.
이를 통해 레이아웃 계산의 오버헤드가 감소합니다.
고정된 크기(내용)의 리스트를 사용할 때 유용합니다.
prototypeItem: ListTile(
title: Text('Sample Item'),
),
proxyDecorator(옵션)
드래그 중인 항목의 외관을 변경할 수 있습니다.
다음은 드래그 중인 항목이 반투명해집니다.
proxyDecorator: (child, index, animation) {
return Material(
elevation: 6.0,
color: Colors.transparent,
shadowColor: Colors.black,
child: child,
);
},
buildDefaultDragHandles(옵션)
(PC나 스마트폰의) 기본 드래그 핸들을 사용할지 여부를 지정할 수 있습니다.
다음은 기본이 아닌 ReorderableDragStartListener로 핸들링하고 있습니다.
buildDefaultDrag
Handles: false,
itemBuilder: (context, index) {
return ListTile(
key: ValueKey(_items[index]),
title: Text(_items[index]),
trailing: ReorderableDragStartListener(
index: index,
child: Icon(Icons.drag_handle),
),
);
},
padding(옵션)
다양한 위젯에 있는 친숙한 속성입니다. 리스트 전체에 패딩을 추가합니다.
다음과 같이 설정합니다.
padding: const EdgeInsets.only(left: 15, right: 15, top: 5, bottom: 5),
header(옵션)
리스트의 시작 부분에 표시할 위젯입니다. 리스트의 제목으로 사용할 수 있습니다.
리스트의 한 요소이므로 스크롤하면 사라집니다.
항상 표시하고 싶은 경우, 리스트 앞에 위젯을 추가하거나 SliverList를 사용할 필요가 있습니다.
header: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'리스트 헤더',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
),
footer(옵션)
리스트의 끝부분에 표시할 위젯입니다. 사용법은 header와 동일합니다.
footer: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'리스트 푸터',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
),
scrollDirection(옵션)
scrollDirection은 리스트의 스크롤 방향을 지정합니다.
수직 방향(Axis.vertical), 수평 방향(Axis.horizontal)으로 설정할 수 있습니다.
기본값은 수직 방향(Axis.vertical)입니다.
scrollDirection: Axis.horizontal,
reverse(옵션)
reverse는 리스트의 순서를 역순으로 할 수 있습니다.
true로 설정하면 리스트의 표시 순서가 역순이 됩니다. 기본값은 false입니다.
reverse:true,
scrollController(옵션)
프로그램에서 리스트뷰의 스크롤을 세부적으로 제어할 수 있습니다.
scrollController: _scrollController,
다음은 Top 버튼을 눌렀을 때 맨 위로 이동하는 기능을 가진 코드입니다.
class _MainState extends State {
final List _items = List.generate(20, (index) => "Item $index");
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_scrollController.addListener(() {
print("Scroll position: ${_scrollController.position.pixels}");
});
}
void _scrollToTop() {
_scrollController.animateTo(
0.0,
duration: Duration(seconds: 1),
curve: Curves.easeInOut,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('맨 위로'),
actions: [
IconButton(
icon: Icon(Icons.arrow_upward),
onPressed: _scrollToTop,
),
],
),
body: ReorderableListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) {
return ListTile(
key: ValueKey(_items[index]),
title: Text(_items[index]),
trailing: ReorderableDragStartListener(
index: index,
child: Icon(Icons.drag_handle),
),
);
},
scrollController: _scrollController,
onReorder: (oldIndex, newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final item = _items.removeAt(oldIndex);
_items.insert(newIndex, item);
});
},
),
);
}
}
primary(옵션)
예를 들어 중첩된 스크롤뷰의 경우, 자식 리스트에 “primary: false”를 설정하면,
자식 리스트의 스크롤을 우선할 수 있습니다.
반대로 설정하지 않으면 부모 리스트의 스크롤이 우선되기 때문에,
자식 리스트의 스크롤이 부드럽게 되지 않습니다. 기본값은 true입니다.
primary: false,
physics(옵션)
스크롤의 동작을 설정할 수 있습니다.
표준 스크롤 클래스는 다음과 같습니다. 기본값은 ClampingScrollPhysics입니다.
- BouncingScrollPhysics:
리스트의 끝에 도달하면 스크롤이 반발 - ClampingScrollPhysics:
리스트의 끝에 도달하면 스크롤이 고정 - FixedExtentScrollPhysics:
스크롤이 일정한 높이로 위치를 정하는 움직임을 함 - NeverScrollableScrollPhysics:
스크롤이 불가능해짐 - PageScrollPhysics:
페이지뷰 같은 스크롤을 위해 수평 스크롤과 조합하면 편리할 수 있음
physics: BouncingScrollPhysics(),
shrinkWrap(옵션)
shrinkWrap는 리스트가 필요한 만큼의 크기를 가지도록 할지를 지정합니다.
true로 설정하면 리스트는 그 내용에 따라 자신의 높이를 결정합니다.
기본값은 false입니다.
shrinkWrap: true,
anchor(옵션)
리스트의 표시 위치를 설정할 수 있습니다.
설정하면 첫 번째 항목의 위에 공간이 생깁니다. 범위는 0~1입니다.
(어떤 상황에서 사용할 수 있을지 이미지가 떠오르지 않네요…)
anchor: 0.1,
cacheExtent(옵션)
스크롤뷰가 표시 영역 외에 얼마나 많은 위젯을 캐시할지를 제어할 수 있습니다.
기본적으로는 아주 적은 양만 캐시하기 때문에 스크롤 성능을 개선하기 위해
이용할 수 있습니다.
하지만 기본값도 충분히 빠르기 때문에 소량이고 크기가 작은 리스트에는 많이 필요하지 않을 수 있습니다.
dragStartBehavior(옵션)
드래그 작업의 터치 이벤트 시작 시 동작을 제어할 수 있습니다.
- DragStartBehavior.start:
드래그 동작은 사용자가 손가락을 움직이기 시작한 순간부터 시작됩니다.
애니메이션이 더 부드럽습니다. 기본값은 이 설정입니다. - DragStartBehavior.down:
다운 이벤트가 처음 감지된 위치에서 시작됩니다.
드래그 동작의 반응이 조금 더 나아집니다.
dragStartBehavior: DragStartBehavior.down,
하지만 체감상으로는 둘 다 비슷했습니다. 신경 쓰이는 경우 설정을 고려하세요.
keyboardDismissBehavior(옵션)
사용자가 스크롤할 때 키보드를 닫을지 여부를 설정할 수 있습니다.
- ScrollViewKeyboardDismissBehavior.manual:
사용자가 스크롤해도 키보드가 자동으로 닫히지 않습니다.
기본값은 이 설정입니다. - ScrollViewKeyboardDismissBehavior.onDrag:
사용자가 스크롤하면 키보드가 자동으로 닫힙니다.
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
restorationId(옵션)
리스트 상태 복원을 위한 ID를 설정할 수 있습니다.
스크롤 위치나 입력 내용 등 사용자의 세션 중 상태를 저장하고,
앱이 다시 시작되거나 재생성될 때 그 상태를 복원할 수 있습니다.
기본값은 설정되어 있지 않습니다.
자세한 내용은 Flutter 공식 (링크)을 참조하세요.
restorationId: 'reorderable_list', // 이 ID를 사용하여 복원 등의 처리를 구현합니다.
clipBehavior(옵션)
위젯의 경계를 넘어서는 부분을 어떻게 자를지를 제어하는 데 사용됩니다.
기본값은 Clip.hardEdge가 설정되어 있습니다.
- Clip.none:
부모의 경계를 넘어 그려지는 것을 허용합니다. - Clip.hardEdge:
부모의 경계를 넘는 부분은 표시되지 않습니다. - Clip.antiAlias:
부모의 경계를 넘는 부분을 안티앨리어스 처리(겹친 부분을 매끄럽게 처리)합니다.
더 부드러운 에지가 되지만, 성능에 영향을 미칠 수 있습니다. - Clip.antiAliasWithSaveLayer:
부모의 경계를 넘는 부분을 안티앨리어스 처리합니다.
고품질 렌더링을 제공하지만 성능 비용이 높아집니다.
clipBehavior: Clip.antiAlias,
Clip.antiAlias와 Clip.antiAliasWithSaveLayer는 텍스트 리스트에서는 크게 효과를
발휘하지 않을 수 있지만, 이미지나 도형을 사용한 리스트에서는 유효할 수 있습니다.
요약
정렬 가능한 ReorderableListView.builder를 소개했습니다.
속성을 설정함으로써 다양한 동작을 커스터마이즈할 수 있을 것 같습니다.
어떤 속성이 있는지 알아두기만 해도 구현의 폭이 넓어질 것 같습니다.