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

Flutter[플러터] / flutter_slidable 패키지 사용법 (스와이프, 슬라이더, ListTile, 리스트 타일, Dismissible, SlidableAction) flutter_slidable (Flutter Widget of the Week)

by ch5c 2025. 8. 14.
반응형

package:flutter_slidable

방향성 있는 슬라이드 동작과 해제가 가능한 슬라이드 목록 항목에 대한 Flutter 구현입니다.

https://youtu.be/QFcFEpFmNJ8


 

리스트 타일이 나열되어 있고 그중에서 자신이 원하는 아이템을 선택하고 그 화면에서 그 아이템에 대한 어떠한 동작을 결정하게 하려면 어떠한 방식이 가장 좋을까? 가장 좋은 방식 중 하나로는 그 아이템을 드래그, 슬라이드 동작을 진행하여 몇 가지 작업을 보여주는 것일 것이다. 이러한 동작을 쉽게 구현할 수 있게 도와주는 위젯이 바로 이번에 알아볼 package:flutter_slidable이다.

flutter_slidable 패키지는 슬라이드 가능한 리스트 항목(Slidable list items)을 만들기 위한 매우 유용한 패키지로 이 패키지를 사용하면 사용자가 리스트 아이템을 좌우로 스와이프 해서 삭제, 수정, 공유 등의 액션을 트리거할 수 있는 인터페이스를 구현할 수 있게 된다.

 

일단 사용하기 위해선 먼저 프로젝트의 pubspec.yaml 파일 안에 animations 패키지를 추가해야 할 것이다.

펍데브(pub.dev)에서 가져와서 프로젝트에 추가해 준다.

flutter pub add flutter_slidable

pubspec.yaml 파일 안에 문제없이 들어갔다면 이제 바로 사용해 주면 된다. 바로 사용해 보자.

가장 먼저 Slidable위젯을 사용해 주면 되는데 이 위젯은 필수 파라미터로 child를 받는다. 근데 이 위젯이 사용되는 위치가 어디인가? 바로 ListTile이다. 그러므로 LisitTile을 자식으로 넣어주면 된다.

Slidable(
  child: ListTile(
    title: Text('item'),
  ),
)

자 이렇게 하면 사용을 할 아주 기본적인 준비가 완료되었다.

이대로 실행해도 오류 없이 돌아가긴 할 텐데 그냥 이 상태는 ListTile 그 자체라 뭐 다른 것은 없을 것이다.

그렇다면 이제 스와이프 하여 기능 목록은 어떻게 열어야 하는 걸까? 바로 startActionPane파라미터와 endActionPane 파라미터를 사용해 주면 된다.

이 두 파라미터는 각각 왼쪽(start)과 오른쪽(end)으로 슬라이드 할 수 있도록 도와주는데 어차피 둘에 들어가는 코드는 다를 것이 없으니 startActionPane를 사용해 주겠다.

Slidable(
  startActionPane: ActionPane(
    // TODO
  ),
  child: ListTile(
    title: Text('item'),
  ),
)

startActionPaneendActionPane파라미터에는 공통적으로 AtionPane위젯을 사용해 주면 된다.

이제 이 위젯 안에서 동작과 슬라이드 후 나올 위젯을 지정해 주면 되겠다.

사용하기 위해서 알아야 AtionPane위젯의 필수 파라미터로는 motionchildren이 있다.

특히 이 motion파라미터에서 리스트 타일을 슬라이드 했을 때 나타나는 애니메이션 방식을 지정해 줄 수 있게 되는데 그 애니메이션 방식들은 아래와 같다. (전부 위젯 형태이다.)

Behind Motion

Drawer Motion

Scroll Motion

Stretch Motion

 

이 모션을 지정해 줄 수 있는 위젯들을 사용해서 어떻게 나타날지 정해주면 된다.

나는 공식 문서 예제에서 사용하는 ScrollMotion을 사용해 주었다.

startActionPane: ActionPane(
  motion: ScrollMotion(),
),

그다음은 이제 children파라미터인데 이 파라미터 안에서 이제 슬라이더를 했을 때 나타는 위젯을 선언해 줄 수 있다.

startActionPane: ActionPane(
  motion: ScrollMotion(),
  children: [
    SlidableAction(
      onPressed: (context) {},
    ),
  ],
),

 

SlidableAction위젯을 사용하여 배치해 줄 수 있는데 기본적으로 필수로 onPressed를 파라미터로 주기 때문에 context를 인자로 한 콜백함수를 넣어줘야 한다.

이제 코드에서 아무런 오류가 나지 않을 텐데 그렇다고 실행하지 않길 바란다. (실행하면 아래와 같이 나온다.)

왜냐하면 이 SlidableAction 위젯은 보이는 것과 다르게 필수 파라미터를 두 개 더 받기 때문인데 바로 iconlabel파라미터이다. 이 두 파라미터에는 이름으로 유추할 수 있듯이 그냥 아이콘과 라벨(텍스트)을 넣어주면 오류 없이 실행할 수 있게 된다.

Slidable(
  startActionPane: ActionPane(
    motion: ScrollMotion(),
    children: [
      SlidableAction(
        onPressed: (context) {},
        icon: Icons.add,
        label: 'Add',
      ),
    ],
  ),
  child: ListTile(
    title: Text('item'),
  ),
),

위의 GIF에 보이는 것처럼 현재 startActionPane를 사용해 줬기 때문에 왼쪽으로 슬라이더 시 위젯이 나타나게 된다.

눌렀을 때 기본적인 동작은(onPressed에 아무것도 기입하지 않을 시) 다시 원상태로 복구되는 것인데 이제 여기에 무엇을 넣을지는 선택의 영역이 되겠다.

그런데 이제 여기에서 슬라이더를 끝까지 당겨서 삭제하는, 즉 Dismissible위젯 같은 동작을 하려면 어떻게 해야 할까?

바로 ActionPane위젯의 파라미터인 dismissible을 사용해 주면 된다.

dismissible: DismissiblePane(onDismissed: () {},),

 

dismissible파라미터 안에는 DismissiblePane위젯을 넣어주면 되는데 이 위젯을 넣어주면 이제 슬라이더가 끝까지 당겨지게 된다.

근데 여기서 문제가 발생한다. 바로 당겨보면 아래와 같은 오류가 뜬다는 것이다.

근데 사실 이 오류를 해결하는 것은 아주 간단하다. 바로 Slidable위젯에 키 값을 먹여주면 된다.

Slidable(
  key: ValueKey(1), // 추가!
  startActionPane: ActionPane(
    dismissible: DismissiblePane(onDismissed: () {},),
    motion: ScrollMotion(),
    children: [
      SlidableAction(
        onPressed: (context) {},
        icon: Icons.add,
        label: 'Add',
      ),
    ],
  ),
  child: ListTile(
    tileColor: Theme.of(context).canvasColor,
    title: Text('item'),
  ),
)

위의 GIF처럼 슬라이더를 끝까지 당기면 리스트 타일이 삭제가 되는 것을 볼 수 있다.

근데 이제 위젯이 잘 보이지 않는다는 문제가 있는데 이것의 해결 방법은 아주 간단하다. 바로 색을 먹여주면 된다.

SlidableAction(
  backgroundColor: Colors.blue.shade100,
  onPressed: (context) {},
  icon: Icons.add,
  label: 'Add',
),

SlidableAction위젯에서 backgroundColor파라미터를 사용해 주면 간단하게 위젯의 배경에 색을 먹여줄 수 있게 된다.

SlidableAction위젯에는 다른 속성들도 있긴 한데 솔직히 쓸만한 건 이거밖에 없지 않나 싶다.

 

이제 기능적인 부분은 이게 끝인데 이제 여기서 알아야 할 것은 ActionPane위젯의 children파라미터는 속성이 List<Widget>이라는 것이다. 그런 만큼 SlidableAction위젯을 여러 개 배치할 수 있는데 이러한 점을 이용해 아래와 같이 만들 수 있다. (아래는 package:flutter_slidable의 공식 예제 코드이다.)

import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
  late final controller = SlidableController(this);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Slidable Example',
      home: Scaffold(
        body: ListView(
          children: [
            Slidable(
              key: const ValueKey(0),
              startActionPane: ActionPane(
                motion: const ScrollMotion(),
                dismissible: DismissiblePane(onDismissed: () {}),
                children: const [
                  SlidableAction(
                    onPressed: doNothing,
                    backgroundColor: Color(0xFFFE4A49),
                    foregroundColor: Colors.white,
                    icon: Icons.delete,
                    label: 'Delete',
                  ),
                  SlidableAction(
                    onPressed: doNothing,
                    backgroundColor: Color(0xFF21B7CA),
                    foregroundColor: Colors.white,
                    icon: Icons.share,
                    label: 'Share',
                  ),
                ],
              ),
              endActionPane: ActionPane(
                motion: const ScrollMotion(),
                children: [
                  SlidableAction(
                    flex: 2,
                    onPressed: (_) => controller.openEndActionPane(),
                    backgroundColor: const Color(0xFF7BC043),
                    foregroundColor: Colors.white,
                    icon: Icons.archive,
                    label: 'Archive',
                  ),
                  SlidableAction(
                    onPressed: (_) => controller.close(),
                    backgroundColor: const Color(0xFF0392CF),
                    foregroundColor: Colors.white,
                    icon: Icons.save,
                    label: 'Save',
                  ),
                ],
              ),
              child: const ListTile(title: Text('Slide me')),
            ),
            Slidable(
              controller: controller,
              key: const ValueKey(1),
              startActionPane: const ActionPane(
                motion: ScrollMotion(),
                children: [
                  SlidableAction(
                    onPressed: doNothing,
                    backgroundColor: Color(0xFFFE4A49),
                    foregroundColor: Colors.white,
                    icon: Icons.delete,
                    label: 'Delete',
                  ),
                  SlidableAction(
                    onPressed: doNothing,
                    backgroundColor: Color(0xFF21B7CA),
                    foregroundColor: Colors.white,
                    icon: Icons.share,
                    label: 'Share',
                  ),
                ],
              ),
              endActionPane: ActionPane(
                motion: const ScrollMotion(),
                dismissible: DismissiblePane(onDismissed: () {}),
                children: const [
                  SlidableAction(
                    flex: 2,
                    onPressed: doNothing,
                    backgroundColor: Color(0xFF7BC043),
                    foregroundColor: Colors.white,
                    icon: Icons.archive,
                    label: 'Archive',
                  ),
                  SlidableAction(
                    onPressed: doNothing,
                    backgroundColor: Color(0xFF0392CF),
                    foregroundColor: Colors.white,
                    icon: Icons.save,
                    label: 'Save',
                  ),
                ],
              ),
              child: const ListTile(title: Text('Slide me')),
            ),
          ],
        ),
      ),
    );
  }
}

void doNothing(BuildContext context) {}

혹은 ListView.Builder를 사용하여 타일 여러 개를 한 번에 제어하게 할 수도 있다.

import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: SlidableExample(),
    );
  }
}

class SlidableExample extends StatelessWidget {
  final List<String> items = List.generate(10, (i) => 'Item {i + 1}');

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Slidable List')),
      body: ListView.builder(
        itemCount: items.length,
        itemBuilder: (context, index) {
          return Slidable(
            key: ValueKey(items[index]),
            startActionPane: ActionPane(
              motion: const DrawerMotion(),
              children: [
                SlidableAction(
                  onPressed: (context) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(content: Text('공유: {items[index]}')),
                    );
                  },
                  backgroundColor: Colors.blue,
                  foregroundColor: Colors.white,
                  icon: Icons.share,
                  label: '공유',
                ),
              ],
            ),
            endActionPane: ActionPane(
              motion: const DrawerMotion(),
              children: [
                SlidableAction(
                  onPressed: (context) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(content: Text('삭제: {items[index]}')),
                    );
                  },
                  backgroundColor: Colors.red,
                  foregroundColor: Colors.white,
                  icon: Icons.delete,
                  label: '삭제',
                ),
              ],
            ),
            child: ListTile(
              title: Text(items[index]),
            ),
          );
        },
      ),
    );
  }
}

이렇게 package:flutter_slidable에 대해서 알아보았다. 이 패키지는 Slidable위젯 하나에 몰빵한 특이한 케이스의 패키지인데 그래서인지 이 Slidable위젯만 알고 있으면 '이 패키지를 완벽하게 사용이 가능하다'라는 스탠스가 가능하다..

이 패키지로 하여금 Dismissible위젯을 사용하지 않고 이 위젯으로 사용하여 더욱 유연하게 상황 대처가 가능해질 것이기에 꼭 알아뒀으면 한다. 도움이 되었길 바라며 마치겠다.

반응형