본문 바로가기
flutter/Widget of the Week

Flutter[플러터] / Dismissible 을 사용하여 리스트에서 스와이프하여 아이템 제거하기 (드래그, 리스트, 디스미스블, 삭제, Swipe, drag) Dismissible (Flutter Widget of the Week)

by ch5c 2025. 5. 17.
반응형

Dismissible class

표시된 방향으로 드래그하여 닫을 수 있는 위젯입니다.

DismissDirection 에서 이 위젯을 드래그하거나 던지면 자식 위젯이 슬라이드 아웃됩니다. 슬라이드 애니메이션 후 resizeDuration 이 null이 아니면 Dismissible 위젯은 resizeDuration 에 지정된 시간 동안 높이(또는 해제 방향과 수직인 너비)를 0으로 애니메이션 합니다.

https://youtu.be/iEMgjrfuc58

 

공식 문서 코드

 


Dismissible 위젯은 리스트나 아이템등 카드처럼 표시된 위젯을 스와이프로 지울 수 있도록 도와주는 위젯인데 보통 위의 예제처럼 주로 ListView.builder 같은 코드와 함께 사용된다. 주요 기능으로는 스와이프 방향설정(가로, 세로)과 삭제 확인, 콜백을 제공해 주는다는 점을 꼽을 수 있겠다. 한번 알아보자.

하위 속성
속성명 타입 기본값 설명
child Widget 실제로 화면에 보여질 자식 위젯
background Widget? null 주로 오른쪽 또는 아래로 드래그할 때 뒤에 보여지는 배경 위젯
secondaryBackground Widget? null 왼쪽 또는 위로 드래그할 때 뒤에 보여지는 배경 위젯
confirmDismiss ConfirmDismissCallback? null 삭제 전 확인을 위한 콜백 함수 (true 반환 시 삭제)
onResize VoidCallback? null 위젯이 축소(resize)될 때 호출되는 콜백
onDismissed DismissDirectionCallback? null 완전히 삭제된 후 호출되는 콜백 함수
direction DismissDirection DismissDirection.horizontal 스와이프 가능한 방향 설정 (horizontal, vertical 등)
resizeDuration Duration? 300ms 삭제 시 위젯이 축소되는 애니메이션 시간
dismissThresholds Map<DismissDirection, double> {} 삭제로 판단되는 드래그 거리 비율 (0.0~1.0)
movementDuration Duration 200ms 위젯 이동 애니메이션 시간
crossAxisEndOffset double 0.0 삭제된 후 위젯이 이동할 보조 축 방향 거리
dragStartBehavior DragStartBehavior DragStartBehavior.start 드래그 시작 위치 기준 설정 (start 또는 down)
behavior HitTestBehavior HitTestBehavior.opaque 히트 테스트 시 동작 방식 설정
onUpdate DismissUpdateCallback? null 드래그 중에 호출되는 콜백, 상태에 따른 UI 업데이트 등에 사용

 

기본적인 코드 구성은 이렇게 된다.

class _TestState extends State<Test> {
  List<int> items = List<int>.generate(10, (int index) => index);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView.builder(
        itemCount: items.length,
        itemBuilder: (context, index) {
          return Dismissible(
            background: Container(color: Colors.green,),
            key: ValueKey<int>(items[index]),
            onDismissed: (direction) {
              setState(() {
                items.removeAt(index);
              });
            },
            child: ListTile(title: Text('dd'),),
          );
        },
      ),
    );
  }
}

 

우리가 이 위젯에서 주시해야 할 것은 key 와 onDismissed 인데 key 부터 살펴보겠다.

key

 

이 위젯에서 정말 중요한 속성이다.

Dismissible 위젯은 내부적으로 애니메이션과 상태 관리를 위해 각 위젯을 고유하게 식별할 수 있는 key가 필요하다. 이 key 는 특히 리스트가 변할 때 어떤 위젯이 제거되었고 어떤 위젯이 남아 있는지를 판단하는 핵심 기준이다.

key: ValueKey<int>(items[index]),

예제에서 만든 이 고유 키는 아이템값(0 - 9) 기반으로 만들어져 있는데 이 키 덕분에 Dismissible 위젯은 어떤 데이터를 나타내고 있는지 정확히 알 수 있다. 그니까 쉽게 말하자면 리스트에서 어떤 아이템이 지워졌는지 또는 남아 있는지를 구분하기 위해 key를 사용한다는 것이다.

지금 이 키는 이렇게 선언이 되어 있을 것이다.

items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; // generate 10 했으니까

이제 여기에서 리스트에 있는 3번째에 있는 아이템을 지운다면

items = [0, 1, 3, 4, 5, 6, 7, 8, 9]; // 3 번째 아이템인 2삭제

이렇게 되는 것이다. 그걸 key 로 받고 있는 것이고 이제 이해가 확실히 되었을 것이다.

onDismissed

 

이 콜백은 사용자가 해당 아이템을 드래그하여 완전히 스와이프 해서 화면 밖으로 사라지게 했을 때 실행된다. 그니까 땡기는 와중에는 실행 안된다는 소리이다.

여기서 setState() 를 호출하여 상태 갱신하고 해당 index 의 아이템을 리스트에서 삭제해 주는 역할을 하게 된다.

onDismissed: (direction) {
  setState(() {
    items.removeAt(index); // 좀 위험
  });
},

기본 예제에서 이렇게 되어 있어서 똑같이 따라 코드를 작성하긴 했다만 추천하는 방식은 아니다.

이렇게 하면 삭제 시점에서 index가 가리키는 아이템이 바뀌는 위험이 있다. 그래서 애니메이션 도중에 빌드가 일어난다면 items[index]가 이미 삭제된 상태일 수 있다. 그래서 이렇게 바꿔서 사용하길 바란다.

onDismissed: (direction) {
  final item = items[index];
  setState(() {
    items.remove(item);
  });
},

참고로 이렇게 새로 만든 item 을 인자값으로 넣지 않고 그냥 remove(index) 를 냅다 박아버린다면 아이템을 삭제한 후 수백 줄의 오류 코드를 볼 수 있을 것이다. (나도 알고 싶지 않았다.)

 

암튼 간에 이것까지가 필수로 해야 했던 것들이고 나머지 하위 값도 알아보자면 먼저 backgorund 와 secondaryBackgorund 가 있을 것인데 backgorund 는 말 그대로 드래그할 때 뒤에 보이는 배경색을 지정할 수 있는 곳이다. 근데 특이한 게 컬러를 받는 게 아니라 Widget? 을 받기 때문에 넣고 싶은 걸 넣을 수 있다.

그렇다면 secondaryBackgorund 는 뭐냐? 이제 왼쪽이랑 오른쪽이랑 드래그했을 때 색이 다르게 나오게 하고 싶다면 사용하면 된다.

background: Container(color: Colors.green,),
secondaryBackground: Container(color: Colors.red,),

이렇게 하니까 왼쪽은 초록 오른쪽은 빨강이 나온다.

 

이번에는 이렇게 Dismissible 위젯을 알아보았는데 생각보단 쉽지만은 않은 것 같다. 또 어떻게 보면 되게 쉬운 것 같긴 한데 좀 귀찮은 점이 있는 친구인 것 같다. 암튼 도움이 되었길 바라며 마치겠다.

반응형