Flutter 리스트 드래그해서 위치 바꾸기(ReorderableListView)

2024. 8. 10. 15:09Flutter

 

최근 활동하고 있는 팀의 MVP가 간략하게 나오게 되어서

혼자 연습도 할 겸 만들어보고 있었는데

이제 기획을 해주신 분들께서 리스트뷰인데 

내가 원하는 위치로 순서를 바꿀 수 있는? 리스트 뷰를 생각하고 계신 거 같아서

나도 한 번 만들어보았다!

 

블로그를 찾아보니 ReorderableListView를 사용하면 된다고 해서

처음 사용을 해보았다!

 

class _PracticeState extends State<Practice> {
  @override
  Widget build(BuildContext context) {
    return ReorderableListView.builder(
        itemBuilder: itemBuilder, 
        itemCount: itemCount, 
        onReorder: onReorder
    );
  }

 

위의 코드에서 보이는 것처럼 ReorderableListView.builder를 사용하였다

밑의 itemBuilder에선 각 항목에 대한 리스트를 생성하고

itemCount는 반복할 횟수이고

onReorder은 리스트 항목이 드래그되어 순서가 변경될 때 호출되는 콜백 함수이다

이 함수는 드래그가 끝났을 때 리스트 항목의 위치를 변경하는 로직을 포함한다!

 

 

우선 아직 파이어베이스를 연결하지 않았기 때문에 

아무 데이터를 넣어서 리스트를 만들어준다

List<String> items = ["게임인문학 과제 제출", "사이드 프로젝트 작업하기", "취업역량 개발 스터디"];

 

이후 이렇게 코드를 작성해준다

 

itemCount: items.length,

 

itemCount는 위의 정의해둔 리스트의 길이만큼 구현을 해주고

 

onReorder: (int oldIndex, int newIndex) {
          setState(() {
            if (newIndex > oldIndex) {
              newIndex -= 1;
            }
            final String item = items.removeAt(oldIndex);
            items.insert(newIndex, item);
          });
        },

 

onReorder은 oldIndex와 newIndex를 받아서

각 리스트간의 위치 이동이 가능하게끔 구현한다

 

 

itemBuilder: (context, index) {
          return Container(
            key: ValueKey(items[index]), // 고유 키 설정
            // margin을 padding으로 변경합니다.
            padding: EdgeInsets.symmetric(vertical: 4), // 위아래 패딩을 추가하여 간격 유지
            child: Container(
              decoration: BoxDecoration(
                color: Colors.grey.shade300,
                borderRadius: BorderRadius.circular(8),
              ),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Container(
                    padding: EdgeInsets.all(10),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          "${items[index]}",
                          style: TextStyle(fontWeight: FontWeight.bold),
                        ),
                        Container(
                          margin: EdgeInsets.fromLTRB(0, 5, 0, 0),
                          width: 35,
                          height: 20,
                          decoration: BoxDecoration(
                            color: Colors.grey.shade400,
                            borderRadius: BorderRadius.circular(10),
                          ),
                          child: Align(
                            alignment: Alignment.center,
                            child: Text(
                              "집중",
                              style: TextStyle(
                                fontSize: 10,
                                color: Colors.grey.shade700,
                              ),
                            ),
                          ),
                        )
                      ],
                    ),
                  ),
                  Container(
                    child: IconButton(
                      onPressed: () {},
                      icon: Icon(Icons.drag_handle),
                    ),
                  ),
                ],
              ),
            ),
          );
        },

 

마지막으로 itemBuilder에는 내가 해당 리스트에 넣어야할 내용들을 넣어줘야한다

여기서 중요한 것은

 

key: ValueKey(items[index]), // 고유 키 설정

 

고유한 키값을 설정해줘야한다

이 부분을 구현하지 않으면 오류가 발생한다

 

 

전체코드는 이렇게 된다

class TodayTodoList extends StatefulWidget {
  const TodayTodoList({super.key});

  @override
  State<TodayTodoList> createState() => _TodayTodoListState();
}

class _TodayTodoListState extends State<TodayTodoList> {
  List<String> items = ["게임인문학 과제 제출", "사이드 프로젝트 작업하기", "취업역량 개발 스터디"];

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 300,
      child: ReorderableListView.builder(
        itemCount: items.length,
        onReorder: (int oldIndex, int newIndex) {
          setState(() {
            if (newIndex > oldIndex) {
              newIndex -= 1;
            }
            final String item = items.removeAt(oldIndex);
            items.insert(newIndex, item);
          });
        },
        itemBuilder: (context, index) {
          return Container(
            key: ValueKey(items[index]), // 고유 키 설정
            // margin을 padding으로 변경합니다.
            padding: EdgeInsets.symmetric(vertical: 4), // 위아래 패딩을 추가하여 간격 유지
            child: Container(
              decoration: BoxDecoration(
                color: Colors.grey.shade300,
                borderRadius: BorderRadius.circular(8),
              ),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Container(
                    padding: EdgeInsets.all(10),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          "${items[index]}",
                          style: TextStyle(fontWeight: FontWeight.bold),
                        ),
                        Container(
                          margin: EdgeInsets.fromLTRB(0, 5, 0, 0),
                          width: 35,
                          height: 20,
                          decoration: BoxDecoration(
                            color: Colors.grey.shade400,
                            borderRadius: BorderRadius.circular(10),
                          ),
                          child: Align(
                            alignment: Alignment.center,
                            child: Text(
                              "집중",
                              style: TextStyle(
                                fontSize: 10,
                                color: Colors.grey.shade700,
                              ),
                            ),
                          ),
                        )
                      ],
                    ),
                  ),
                  Container(
                    child: IconButton(
                      onPressed: () {},
                      icon: Icon(Icons.drag_handle),
                    ),
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

 

이렇게 만들어주고 사용은 이렇게 해주었다

 

Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text("오늘 할 일", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),),
                    Text("우선순위 파악이 가능하다면 순서대로 정리해보세요", style: TextStyle(fontSize: 12, color: Colors.grey.shade800),),
                    SizedBox(height: 10,),
                    TodayTodoList()

                  ],
                ),

 

 

그럼 진짜 마지막으로 어떻게 구현이 되었는지 확인해보자

화면 기록 2024-08-10 오후 3.08.34.mov
0.53MB