【Flutter】ListViewのアイテムを外からアニメーションさせる【Animation】

# 問題点

ボタンが押された時に、ListViewの特定のアイテムをアニメーションさせたい。

アイテムをタップでアニメーションは簡単にできるんだけど、ボタンタップ時に外からアニメーションさせるにはひと工夫必要だった。
今回は、ボタンが押されたら特定のアイテムのOpacityを変更して、点滅しているように見えるアニメーションを実装するよ。
完成形はこんな感じ。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474683/957f1a9b-fef2-6efb-9aaf-eee9d322ab42.gif" width="300">

※**コードは簡略化しているので、コピペだと動かないよ。**
サンプルあるから、[ここ](https://github.com/KKusumi/list_view_item_animation_sample)から持っていってね。

# 解決策

GlobalObjectKeyを使えば実現できる。
3ステップで解説していくよ。


## 1. Listを持つWidgetに対してGlobalObjectKeyを設定する。

まず、Listを持つWidgetに対してGlobalObjectKeyを設定する。
そうすると、key経由でSampleListが持つ関数を実行できるようになる。

```dart
// Todo
final listKey = GlobalObjectKey<SampleListState>(context);

return Scaffold(
  body: SampleList(
      // Todo
      key: listKey,
      nameList: nameList,
  ),
  floatingActionButton: FloatingActionButton(
    onPressed: () {
      // Todo
      listKey.currentState?.animate(5);
    },
  ),
);
```

## 2. リストのアイテムにGlobalObjectKeyを設定する。

次に、リストのアイテムにGlobalObjectKeyを設定する。
`initState()`の時に、受け取ったリストの分だけfor文を回して、keyを生成していく。
itemのビルド時に、indexを元にして取得したkeyを渡す。

これで、ボタンが押された時にkey経由でアニメーションの関数を実行できるようになる。

```dart
class SampleList extends StatefulWidget {
  const SampleList({super.key, required this.nameList});

  final List<String> nameList;

  @override
  State<StatefulWidget> createState() => SampleListState();
}

class SampleListState extends State<SampleList> {
  
  // Todo
  final List<GlobalObjectKey<SampleListItemState>> keys = [];

  @override
  void initState() {
    super.initState();
    // Todo
    for (var name in widget.nameList) {
      keys.add(GlobalObjectKey(name));
    }
  }

  @override
  Widget build(BuildContext context) {
    return ListView.separated(
      itemBuilder: (context, index) {
        // Todo
        return SampleListItem(key: keys[index], name: widget.nameList[index]);
      },
      itemCount: widget.nameList.length,
    );
  }

  // Todo
  void animate(int index) {
    keys[index].currentState?.animate();
  }
}

```

## 3. リストのアイテムを実装する。

最後に、アニメーションできるアイテムを実装する。

```dart
class SampleListItem extends StatefulWidget {
  const SampleListItem({super.key, required this.name});

  final String name;

  @override
  State<StatefulWidget> createState() => SampleListItemState();
}

class SampleListItemState extends State<SampleListItem>
    with SingleTickerProviderStateMixin {
  late AnimationController _animationController;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 200),
    );
  }

  @override
  void dispose() {
    super.dispose();
    _animationController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final opacityAnimation = Tween<double>(
      begin: 1.0,
      end: 0.3,
    ).animate(_animationController);

    var opacityAnimationCounter = 0;
    _animationController.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _animationController.reverse();
      } else if (status == AnimationStatus.dismissed) {
        if (opacityAnimationCounter < 2) {
          _animationController.forward();
          opacityAnimationCounter++;
        } else {
          opacityAnimationCounter = 0;
        }
      }
    });

    return FadeTransition(
      opacity: opacityAnimation,
      child: Container(
        color: Colors.teal,
        padding: const EdgeInsets.all(16),
        child: Text(widget.name),
      ),
    );
  }

  void animate() {
    _animationController.forward();
  }
}
```

ボタンが押されると、リストのアイテムの`animate()`が呼び出されて、アニメーションが実行される。

# まとめ

- GlobalObjectKeyを使うと、子要素の関数を実行できる。
- GlobalObjectKeyのリストを生成して保持しておくことで、リストの特定のアイテムに対してアニメーションを実行することができる。

おわり。