Keyって結局いつ使うの?
Flutterを触っている人ならKeyの存在はご存知だと思います。
でも、いつ使うの?という質問に明確に答えられる人は少ないかと思います。
ということで、この記事では__Keyについて詳しくなって自由自在に使えるようになろう!__というお話です。
Keyはほとんど全てのWidgetのコンストラクタで指定できます。
どういう時に利用するのでしょうか?
それは主に次の場合です。
・類似Widgetのコレクションを識別して追加、削除、並び替えをしたい
・ユーザーのスクロール位置を保持したい
リストを入れ替えるアプリ
説明のためにリストを入れ替えるアプリを作ります!
コード。。。どん!
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: KeySample(),
);
}
}
class KeySample extends StatefulWidget {
@override
KeySampleState createState() {
return KeySampleState();
}
}
class KeySampleState extends State<KeySample> {
List<Widget> titles = [
StatefulRandomTitle(key: UniqueKey()),
StatefulRandomTitle(key: UniqueKey())
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("key sample"),
),
body: Column(
children: [
ElevatedButton(
onPressed: swapTitle,
child: const Text("入れ替える"),
),
Column(
children: titles,
)
],
),
);
}
swapTitle() {
setState(() {
titles.insert(1, titles.removeAt(0));
});
}
}
class StatefulRandomTitle extends StatefulWidget {
StatefulRandomTitle({Key key}) : super(key: key);
@override
StatefulRondomTitleState createState() => StatefulRondomTitleState();
}
class StatefulRondomTitleState extends State<StatefulRandomTitle> {
int rondom;
@override
void initState() {
// TODO: implement initState
super.initState();
rondom = Random().nextInt(100);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ListTile(
title: Text(rondom.toString()),
),
],
);
}
}
コードの説明は後でします
動画を準備したので動きを先に見てみましょう!
List<Widget> titles = [
StatefulRandomTitle(key: UniqueKey()),
StatefulRandomTitle(key: UniqueKey()),
];
1番のポイントはStatefulRandomTitleのkeyにUniqueKey()をセットしているところです。
ちなみに、UniqueKeyを与えなければ入れ替えられません。
なんでUniqueKey()を与えなければ、変更されないのか?
詳しくみていきましょう!
Runボタンを押すと、以下のようなツリーが作成されます
Widgetにはそれに対応したElementがあります。
(「Elementなんて作ってないよ?」と思った方のためにまた記事を書きます。)
一旦この図を受け入れてください!
ボタンを押して、入れ替え処理をしている__途中__、StatefulRandomTitleが再構築されますが、Elementの参照先は以前と同じです。
Widget(StatefulRandomTitle)を再構築した__後__、古い方のStatefulRandomTitleと新しいStatefulRandomTitleの型とKey
が同じかどうかをElemetクラスのupdateChild()メソッド内で調べます。
説明のために、古い方のStatefulRandomTitleをStatefulRandomTitle(old)
新しいStatefulRandomTitleをStatefulRandomTitle(new)と定義します!
下の図をみながら説明します。
型とKeyが古いものと全く同じだからElementの参照先がnewのものに変更されます。
(StatefulRandomTitle.keyで調べられます)
もし、Keyを指定しなかった場合は、Keyがnull(同じ)で型が同じなのでElementの参照先が移動します。しかし、UIの更新はされません。
だからボタンを押しても何も反応していないような動きになります。
もう少し詳しく説明しましょう
Keyを指定しない場合
Keyを指定しない場合、
ElementからWidgetの参照は変更されますが、RnderObject(描画処理)が更新されないため何も動いていないように見えます。(StatefulWidgetにrandom変数を持たせれば、Keyがなくても更新できるが可変の変数をStatefulWidgetに持たせるのは良くない設計なのでやっぱりKeyを与えよう)
Widgetが変更したときに,Elementの参照を変えるだけで処理を軽くする試みがされています。