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

※コードは簡略化しているので、コピペだと動かないよ。
サンプルあるから、ここから持っていってね。
解決策
GlobalObjectKeyを使えば実現できる。
3ステップで解説していくよ。
1. Listを持つWidgetに対してGlobalObjectKeyを設定する。
まず、Listを持つWidgetに対してGlobalObjectKeyを設定する。
そうすると、key経由でSampleListが持つ関数を実行できるようになる。
// 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経由でアニメーションの関数を実行できるようになる。
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. リストのアイテムを実装する。
最後に、アニメーションできるアイテムを実装する。
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のリストを生成して保持しておくことで、リストの特定のアイテムに対してアニメーションを実行することができる。
おわり。