Flutter 일기
참고 1: https://api.flutter.dev/flutter/widgets/ReorderableDragStartListener-class.html
참고 2: https://api.flutter.dev/flutter/material/ReorderableListView/buildDefaultDragHandles.html
참고 3: https://api.flutter.dev/flutter/material/ReorderableListView-class.html
ReorderableListView
오늘 배워볼 것은 ReorderableListView.
ListView 랑 똑같은데 사용자가 목록의 순서를 하나하나 재배열할 수 있다.
자식 요소가 너무 많아지면 사용하기 적절치 않다. 그리고 리스트의 아이템들은 모두 key 를 갖고 있어야 한다. 키가 뭘까..?
우선 아래 2가지만 넣어도 잘 동작한다.
required List<Widget> children,
required ReorderCallback onReorder,
코드로 가보자.
코드 예시로 알아보자
오늘도 공식 페이지의 예제를 가져다가 돌려보았다.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return MaterialApp(
title: "ReorderableListView",
home: Scaffold(
appBar: AppBar(title: const Text('ReorderableListView')),
body: const MyStatefulWidget(),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final List<int> _items = List<int>.generate(10, (int index) => index);
Widget build(BuildContext context) {
final ColorScheme colorScheme = Theme.of(context).colorScheme;
final Color oddItemColor = colorScheme.primary.withOpacity(0.05);
final Color evenItemColor = colorScheme.primary.withOpacity(0.15);
return ReorderableListView(
padding: const EdgeInsets.symmetric(horizontal: 20),
children: <Widget>[
for (int index = 0; index < _items.length; index++)
ListTile(
key: Key('$index'),
tileColor: _items[index].isOdd ? oddItemColor : evenItemColor,
title: Text('Item ${_items[index]}'),
trailing: Icon(Icons.drag_handle),
),
],
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final int item = _items.removeAt(oldIndex);
_items.insert(newIndex, item);
});
},
);
}
}
colorScheme.primary 가 무슨 색인지 몰라서 들어가보니, 요렇게 되어있다.
return ColorScheme(
primary: primarySwatch,
...
);
여기서 primarySwatch의 기본값은 파란색이다.
MaterialColor primarySwatch = Colors.blue,
일단 색은 대충 이렇고, ReorderableListView 를 파보자.
children: <Widget>[
for (int index = 0; index < _items.length; index++)
ListTile(
key: Key('$index'), //key 는 각각의 고유한 값만 가지면 되는 듯하다.
tileColor: _items[index].isOdd ? oddItemColor : evenItemColor,
title: Text('Item ${_items[index]}'),
trailing: Icon(Icons.drag_handle),
),
],
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final int item = _items.removeAt(oldIndex);
_items.insert(newIndex, item);
});
},
children 부분은 _items의 length 인 10만큼 ListTile 을 만드는 것으로 간단하다. onReorder 속성의 아래 두줄을 먼저 보면, 이동하려는 리스트 항목의 이동 전 위치(oldIndex)를 지우고 이동 후 위치(newIndex)자리에 다시 집어넣는 방식이다.
그런데 oldIndex < newIndex 일 때는 어째서 newIndex=-1 을 해줘야 하는가? removeAt으로 oldIndex 자리의 항목을 지워버리면, List의 길이가 하나 줄어들기 때문이다. 이 상태에서 새 위치로 그냥 이동하면 이동하려는 위치보다 한칸 더 많이 이동하게 된다. 그리고 만약 맨 마지막 위치로 이동하려고 할 때, 리스트의 범위를 벗어나게 되어 인덱스 에러가 발생한다.
반면 newIndex가 oldIndex보다 작아질 때는 상관없다.
이제 실행화면을 보자.
위처럼 longpress, 조금 오래 눌러야 이동이 활성화된다.
옆에 trailing으로 버튼 만들어놓고 왜 저거 눌러서 옮기지 않았을까?
저걸로 이동이 안된다...
빠름의 민족은 LongPress를 견딜 수 없다
ReorderableListView 안에는 buildDefaultDragHandles 라는 속성이 있다. boolean 타입인데, 기본값은 true로 설정되어 있다.
안드로이드 애뮬레이터가 아닌 Chrome 환경에서 실행을 시켜보니 trailing 속성에 지정했던 Icons.drag_handle 을 빼버려도 이 아이콘이 자동으로 생긴다. 게다가 아이콘을 누르면 longPress가 아닌 그냥 클릭으로도 이동이 가능하다. 왜일까?
buildDefaultDragHandles가 true 이면 Desktop 환경에서는 ReorderableDragStartListener 로 감싼 Icons.drag_handle 이 생성되는 것이 기본 드래그 방식이라고 한다.
하지만 모바일 환경에서는 항목 전체를 ReorderableDelayedDragStartListener로 감싼 것이 기본이다. (참고 2에 가면 있다.)
이름만 봐도 뭔가 차이가 나지 않는가? Delayed... <- Long press의 주범이다.
longpress 를 보자마자 빨리빨리 의 마음이 솟구쳤으니, ReorderableDragStartListener 를 이용해서 빨리 해보자.
코드를 다음과 같이 수정한다.
trailing: ReorderableDragStartListener(
index: index, child: Icon(Icons.drag_handle)),
trailing 부분의 아이콘을 ReorderableDragStartListener로 감싸준 후의 실행화면을 보자.
아이콘 부분을 가볍게 눌러서 이동할 수도 있고, 항목 자체를 오래 눌러서 이동할 수도 있다. buildDefaultDragHandles : false 로 지정하면 항목을 오래 눌러서 이동하는 것은 안된다.
빠름이 찾는다고 한참 걸렸네
오늘의 일기는 여기까지!
내용이 너무 깔끔한 것 같습니다
잘 보고 갑니다!!