Flutter Webでスワイプ削除を実装する

# 概要
よくあるコレ（スワイプ削除）をFlutterで実装する方法です。

![swipe-delete](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F495652%2Fa8b8e499-b68c-c804-1c32-a155038fbae8.gif?ixlib=rb-1.2.2&auto=format&gif-q=60&q=75&w=1400&fit=max&s=f3dbd5fcb84a0ac786319e2ba0adc7b4)

# まずは原型
コードは以下のような状態とします。
```flutter create```を行ったあと、少しだけ手を入れて静的なリストを一覧表示しています。

```dart:main.dart
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<String> itemList = ["item1", "item2", "item3", "item4", "item5"];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: ListView.builder(
        itemCount: itemList.length,
        itemBuilder: (context, index) {
          return Card(
            child: ListTile(
              title: Text(itemList[index]),
            ),
          );
        },
      ),
    );
  }
}
```

見た目はこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495652/4506203f-e0a8-b733-b491-bfed43f951ea.png)


# Dismissibleでラッピングする
まずは、静的リストが生成されているCard部分をDismissibleでラッピングします。

```dart:main.dart(before)
// ～一部抜粋～　以下部分をDismissibleで囲む
return Card(
  child: ListTile(
    title: Text(itemList[index]),
  ),
);
```
このようになります。
なお、Cardがスワイプされた時にどのCardがスワイプされたかが識別できるように一意な値を持たせる必要があります。一意な値は```key```で設定します。何を与えても良いですが、重複しない保証があるUniqueKeyを使用すると良いでしょう。

```dart:main.dart(after)
// ～一部抜粋～　Dismissibleで囲んでみた
return Dismissible(
  key: UniqueKey(), 
  child: Card(
    child: ListTile(
      title: Text(itemList[index]),
    ),
  ),
); 
```
この時点で、以下のようにスワイプ削除機能は完成しています。
![GIF 2021-01-29 1-14-26.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495652/af6b3741-182f-1b77-95bc-99efc67471eb.gif)

ただ、実はスワイプ方向が指定されていないので**左右どちらにでもスワイプ**できてしまいます。

![GIF 2021-01-29 1-24-10.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495652/59e01755-c773-0907-1731-b077d93ed11f.gif)

# スワイプ可能な方向を指定する

さすがに気持ちが悪いので、directionパラメータで方向を指定します。
```DismissDirection.endToStart```が画面上では左から右を表します。
```startToEnd```にすれば右から左になります。

```dart:main.dart
return Dismissible(
  key: UniqueKey(),
  direction: DismissDirection.endToStart, //追加
  child: Card(
    child: ListTile(
      title: Text(itemList[index]),
    ),
  ),
);
```

これで、右から左へはスワイプができなくなりました。

# せっかくなので背景色を...
せっかくなので、スワイプ時に赤い背景が出るようにしてみます。
背景はbackgroundで追加することができます。

```dart:main.dart
return Dismissible(
  key: UniqueKey(),
  direction: DismissDirection.endToStart,
  //backgroundパラメータを追加
  background: Container(
    alignment: AlignmentDirectional.centerEnd,
    color: Colors.red,
    child: Padding(
      padding: EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0),
    ),
  ), //ここまで追加
  child: Card(
    child: ListTile(
      title: Text(itemList[index]),
    ),
  ),
);
```

![GIF 2021-01-29 1-24-10.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495652/3802408a-7327-1a36-859b-9432d91e35d1.gif)


# ついでにアイコンも！
なんか物足りないので、最後にゴミ箱のアイコンを足してみたいと思います。

```dart:main.dart
return Dismissible(
  key: UniqueKey(),
  direction: DismissDirection.endToStart,
  background: Container(
    alignment: AlignmentDirectional.centerEnd,
    color: Colors.red,
    child: Padding(
      padding: EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0),
      //Padding配下にchildを足し、Iconを配置
      child: Icon(
        Icons.delete,
        color: Colors.white,
      ),//ここまで追加
    ),
  ),
  child: Card(
    child: ListTile(
      title: Text(itemList[index]),
    ),
  ),
);
```
Paddingの配下に新しく子要素（child）を作成し、Iconを置きます。配置できるアイコンは多数が既に用意されているものなので、新しく導入する必要は殆どないでしょう。
利用可能なIconの一覧は<a href="https://api.flutter.dev/flutter/material/Icons-class.html">ここ</a>で確認できます。

というわけで、無事に実装完了です！せっかくですので動きを確認してみましょう。
![GIF 2021-01-29 1-24-101.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495652/fce563a0-3d92-4965-8ace-280d86991f98.gif)

# コード全体
全体のコードは以下のようになりました。

```dart:main.dart
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<String> itemList = ["item1", "item2", "item3", "item4", "item5"];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: ListView.builder(
        itemCount: itemList.length,
        itemBuilder: (context, index) {
          return Dismissible(
            key: UniqueKey(),
            direction: DismissDirection.endToStart,
            //backgroundパラメータを追加
            background: Container(
              alignment: AlignmentDirectional.centerEnd,
              color: Colors.red,
              child: Padding(
                padding: EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0),
                child: Icon(
                  Icons.delete,
                  color: Colors.white,
                ),
              ),
            ),
            child: Card(
              child: ListTile(
                title: Text(itemList[index]),
              ),
            ),
          );
        },
      ),
    );
  }
}

```

# まとめ
* CardをDismissibleでラッピングすればとりあえずスワイプ削除は実現できる
* 方向指定はDismissible配下のdirectionで
* 背景色、アイコンも設定できるよ！