key
パラメーターは、ほとんどすべてのウィジェットコンストラクターで見つけることができますが、このパラメーターは開発ではほとんど使用されません。 Keys
は、ウィジェットツリー内のウィジェットを移動するときに状態を保持します。 実際には、これは、コレクションの変更時にユーザーのスクロール位置を保存したり、状態を保存したりするのに役立つ可能性があることを意味します。
この記事は、次のビデオからの抜粋です。 読むよりも聞く/見ることを好む場合、ビデオは同じ素材を提供します。
keys
に関する秘密情報
ほとんどの場合... keys
は必要ありません。 一般に、それらを追加しても害はありませんが、新しい変数の両側で新しいキーワードまたは型宣言として行われるため、これも必要ありません(私はあなたについて話している、 Map<Foo, Bar> aMap = Map<Foo, Bar>()
)。
ただし、何らかの状態を含み、同じタイプのウィジェットをコレクション内で追加、削除、または再配置している場合は、keys
注意する必要がありkeys
!
ウィジェットのコレクションを変更するときにkeys
が必要な理由を示すために、ボタンがクリックされたときに場所を変更する2つのカラフルなウィジェットを持つ非常にシンプルなアプリケーションを作成しました。
このバージョンのアプリケーションでは、色ウィジェットの順序を格納するために、 Row
とStateedTilesウィジェットに状態( StatefulWidget
)のランダムな色の2つのステートレスウィジェット( StatelessWidget
)があります。 下のFloatingActionButton
ボタンをクリックすると、色ウィジェットがリスト内の位置を正しく変更します。
void main() => runApp(new MaterialApp(home: PositionedTiles())); class PositionedTiles extends StatefulWidget { @override State<StatefulWidget> createState() => PositionedTilesState(); } class PositionedTilesState extends State<PositionedTiles> { List<Widget> tiles = [ StatelessColorfulTile(), StatelessColorfulTile(), ]; @override Widget build(BuildContext context) { return Scaffold( body: Row(children: tiles), floatingActionButton: FloatingActionButton( child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles), ); } swapTiles() { setState(() { tiles.insert(1, tiles.removeAt(0)); }); } } class StatelessColorfulTile extends StatelessWidget { Color myColor = UniqueColorGenerator.getColor(); @override Widget build(BuildContext context) { return Container( color: myColor, child: Padding(padding: EdgeInsets.all(70.0))); } }
しかし、カラーウィジェットに状態を追加して( StatefulWidget
)、色を保存すると、ボタンをクリックすると、何も起きていないように見えます。
List<Widget> tiles = [ StatefulColorfulTile(), StatefulColorfulTile(), ]; ... class StatefulColorfulTile extends StatefulWidget { @override ColorfulTileState createState() => ColorfulTileState(); } class ColorfulTileState extends State<ColorfulTile> { Color myColor; @override void initState() { super.initState(); myColor = UniqueColorGenerator.getColor(); } @override Widget build(BuildContext context) { return Container( color: myColor, child: Padding( padding: EdgeInsets.all(70.0), )); } }
説明として:上記のコードは、ユーザーがボタンをクリックしても色の交換が表示されないという点でバグがあります。 このエラーを修正するには、色付きのStatefulWidget
ウィジェットにkey
パラメーターを追加する必要があります。そうすると、ウィジェットは必要に応じて交換されます。
List<Widget> tiles = [ StatefulColorfulTile(key: UniqueKey()), // Keys added here StatefulColorfulTile(key: UniqueKey()), ]; ... class StatefulColorfulTile extends StatefulWidget { StatefulColorfulTile({Key key}) : super(key: key); // NEW CONSTRUCTOR @override ColorfulTileState createState() => ColorfulTileState(); } class ColorfulTileState extends State<ColorfulTile> { Color myColor; @override void initState() { super.initState(); myColor = UniqueColorGenerator.getColor(); } @override Widget build(BuildContext context) { return Container( color: myColor, child: Padding( padding: EdgeInsets.all(70.0), )); } }
ただし、これは、変更するサブツリーに状態を持つウィジェットがある場合にのみ必要です。 コレクション内のウィジェットのサブツリー全体に状態がない場合、キーは必要ありません。
行くぞ! すべてのすべて、 Flutter
keys
を使用するために知っておく必要があるすべて。 しかし、あなたが起こっていることに少し深く行きたいなら...
keys
必要な場合keys
ある理由を理解する
あなたはまだここにいますよね? それでは、要素ツリーとウィジェットの真の性質を調べて、フラッターメイジになろう! わははは! ハハ ハハ ごめんなさい
ご存じのとおり、各ウィジェット内でFlutterは対応する要素を作成します。 Flutterがウィジェットツリーを作成するように、要素ツリー(ElementTree)も作成します。 ElementTreeは非常にシンプルで、各ウィジェットのタイプ情報と子要素へのリンクのみが含まれています。 ElementTreeをFlutterアプリケーションのスケルトンと考えることができます。 アプリケーションの構造を示していますが、追加情報はすべてソースウィジェットへのリンクにあります。
上記の例の行ウィジェットには、その子ごとに順序付けられたスロットのセットが含まれています。 Rowのカラーウィジェットの順序を変更すると、FlutterはElementTreeを歩き回り、アプリケーションのスケルトン構造が同じかどうかを確認します。
検証はRowElementで始まり、次に子要素に進みます。 ElementTreeは、新しいウィジェットが古いものと同じタイプとkey
を持っていることをチェックし、もしそうなら、要素は新しいウィジェットへのリンクを更新します。 コードのステートレスバージョンでは、ウィジェットにkey
がないため、Flutterはタイプのみをチェックします。 (一度に情報が多すぎる場合は、上記のアニメーションチャートを参照してください。)
状態ウィジェットのElementTreeの下は少し異なります。 前と同じようにウィジェットと要素がありますが、ウィジェットの状態オブジェクトもいくつかあり、色情報はウィジェット自体ではなく、それらに保存されます。
key
ない色付きのStatefulWidget
ウィジェットの場合、2つのウィジェットの順序を変更すると、FlutterはElementTreeを巡回し、RowWidgetのタイプをチェックして、リンクを更新します。 次に、カラーウィジェット要素は、対応するウィジェットが同じタイプであることを確認し、リンクを更新します。 2番目のウィジェットでも同じことが起こります。 FlutterはElementTreeとそれに対応する状態を使用して、実際にデバイスに表示するものを決定するため、私たちの観点からは、ウィジェットは交換されていないようです!
コンストラクターに状態を持つ色付きウィジェットのコードの修正バージョンでは、 key
プロパティを定義しました。 ここで、 Row
のウィジェットを変更すると、タイプごとに以前と同じように一致しますが、カラーウィジェットとElementTreeの対応する要素のkey
値は異なります。 これにより、Flutterはカラーウィジェットのこれらの要素を非アクティブ化し、ElementTreeでそれらの要素へのリンクを削除します。最初のkey
はkey
一致しません。
その後、Flutterは、対応するkey
を使用してElementTreeのRow
要素でウィジェットを検索しkey
。 一致する場合、ウィジェット要素にリンクを追加します。 Flutterは、リンクのない各子に対して行います。 これでFlutterに期待どおりの結果が表示され、ボタンをクリックするとカラーウィジェットの場所が変わります。
したがって、 keys
は、コレクション内の状態でウィジェットの順序または数を変更する場合に役立ちます。 この例では、色を保存しました。 ただし、多くの場合、状態はそれほど明白ではありません。 アニメーションの再生、ユーザー入力の表示、場所のスクロールなど、すべての状態があります。
いつkeys
を使用する必要がありkeys
か?
簡単な答え: keys
をアプリケーションに追加する必要がある場合は、保存したい状態のウィジェットサブツリーの上部にkeys
を追加する必要があります。
私が見たよくある間違いは、状態を持つ最初のウィジェットに対してのみkey
を定義する必要があると人々が考えることですが、ニュアンスがあります。 信じられない? どのような問題があるのかを示すために、カラーウィジェットのキーを残したまま、カラーウィジェットをPadding
ウィジェットでラップしました。
void main() => runApp(new MaterialApp(home: PositionedTiles())); class PositionedTiles extends StatefulWidget { @override State<StatefulWidget> createState() => PositionedTilesState(); } class PositionedTilesState extends State<PositionedTiles> { // Stateful tiles now wrapped in padding (a stateless widget) to increase height // of widget tree and show why keys are needed at the Padding level. List<Widget> tiles = [ Padding( padding: const EdgeInsets.all(8.0), child: StatefulColorfulTile(key: UniqueKey()), ), Padding( padding: const EdgeInsets.all(8.0), child: StatefulColorfulTile(key: UniqueKey()), ), ]; @override Widget build(BuildContext context) { return Scaffold( body: Row(children: tiles), floatingActionButton: FloatingActionButton( child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles), ); } swapTiles() { setState(() { tiles.insert(1, tiles.removeAt(0)); }); } } class StatefulColorfulTile extends StatefulWidget { StatefulColorfulTile({Key key}) : super(key: key); @override ColorfulTileState createState() => ColorfulTileState(); } class ColorfulTileState extends State<ColorfulTile> { Color myColor; @override void initState() { super.initState(); myColor = UniqueColorGenerator.getColor(); } @override Widget build(BuildContext context) { return Container( color: myColor, child: Padding( padding: EdgeInsets.all(70.0), )); } }
今、ボタンに触れるだけで、ウィジェットは完全にランダムな色になります!
これは、追加されたPadding
ウィジェットを使用したウィジェットツリーとElementTreeの外観です。
子ウィジェットの位置を変更すると、要素とウィジェット間のマッチングアルゴリズムは、要素のツリーの1つのレベルに見えます。 図では、子の子が暗くされているため、最初のレベルから何も邪魔されません。 このレベルでは、すべてが正しく一致します。
2番目のレベルでは、Flutter key
color要素のkey
ウィジェットのkey
一致しないことに気づくため、この要素を非アクティブ化し、破棄して、すべてのリンクを削除します。 この例で使用するkeys
はLocalKeys
です。 つまり、ウィジェットを要素と一致させると、Flutterはツリーの特定のレベルでのみkeys
検索しkeys
。
彼はこのレベルで対応するkey
持つカラーウィジェット要素を見つけることができないため、新しいウィジェットを作成して新しい状態を初期化し、この場合ウィジェットをオレンジ色にします!
Padding
ウィジェットのkeys
を定義する場合:
void main() => runApp(new MaterialApp(home: PositionedTiles())); class PositionedTiles extends StatefulWidget { @override State<StatefulWidget> createState() => PositionedTilesState(); } class PositionedTilesState extends State<PositionedTiles> { List<Widget> tiles = [ Padding( // Place the keys at the *top* of the tree of the items in the collection. key: UniqueKey(), padding: const EdgeInsets.all(8.0), child: StatefulColorfulTile(), ), Padding( key: UniqueKey(), padding: const EdgeInsets.all(8.0), child: StatefulColorfulTile(), ), ]; @override Widget build(BuildContext context) { return Scaffold( body: Row(children: tiles), floatingActionButton: FloatingActionButton( child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles), ); } swapTiles() { setState(() { tiles.insert(1, tiles.removeAt(0)); }); } } class StatefulColorfulTile extends StatefulWidget { StatefulColorfulTile({Key key}) : super(key: key); @override ColorfulTileState createState() => ColorfulTileState(); } class ColorfulTileState extends State<ColorfulTile> { Color myColor; @override void initState() { super.initState(); myColor = UniqueColorGenerator.getColor(); } @override Widget build(BuildContext context) { return Container( color: myColor, child: Padding( padding: EdgeInsets.all(70.0), )); } }
Flutterは問題を認識し、前の例のようにリンクを正しく更新します。 ユニバースの順序が復元されます。
どのタイプのKey
を使用する必要がありますか?
Flutter APIにより、いくつかのKey
クラスを選択できました。 使用すべきkey
のタイプは、 key
を必要とする要素の特徴的な機能によって異なりkeys
。 それぞれのウィジェットに保存した情報を見てください。
次のTo-doアプリケーション[1]を検討してください。ここでは、優先度に基づいてタスクリスト内のアイテムの順序を変更でき、完了したら削除できます。
ValueKey
この場合、実行するアイテムのテキストは永続的で一意であることが予想されます。 もしそうなら、これはおそらくテキストが「値」であるValueKey
良い候補です。
return TodoItem( key: ValueKey(todo.task), todo: todo, onDismissed: (direction) => _removeTodo(context, todo), );
オブジェクトキー
または、各ユーザーに関する情報を一覧表示するアドレス帳アプリケーションを使用することもできます。 この場合、各子ウィジェットには、より複雑なデータの組み合わせが格納されます。 名前や誕生日などの個々のフィールドは別のエントリと一致する場合がありますが、組み合わせは一意です。 この場合、 ObjectKey
が最適です。
ユニークキー
コレクションに同じ値を持つ複数のウィジェットがある場合、または各ウィジェットが他のすべてのウィジェットと異なることを本当に確認したい場合は、 UniqueKey
を使用できます。 サンプルアプリケーションで色を切り替えるためにUniqueKey
を使用しました。 UniqueKey
は、ウィジェットに格納される他の定数データがなく、作成時にウィジェットの色がわからないためです。
ただし、 key
として使用したくないのは乱数です。 ウィジェットが作成されるたびに、新しい乱数が生成され、フレーム間の一貫性が失われます。 このシナリオでは、 keys
をまったく使用できません!
PageStorageKeys
PageStorageKeys
は、スクロールの現在の状態を含む特殊なkeys
あるため、アプリケーションは後で使用するために保存できます。
グローバルキー
GlobalKeys
を使用するための2つのオプションがあります。ウィジェットを使用すると、状態を失うことなくアプリケーション内の任意の場所で親を変更でき、ウィジェットツリーのまったく異なる部分の別のウィジェットに関する情報にアクセスできます。 最初のシナリオの例として、2つの異なる画面に同じウィジェットを表示したいが、同じ状態でウィジェットデータを保存するためにGlobalKey
を使用することをGlobalKey
ます。 2番目のケースでは、パスワードを確認する必要があるが、ツリー内の他のウィジェットとステータス情報を共有したくない場合があります。 GlobalKeys
は、 key
を使用して特定のウィジェットにアクセスし、そのステータスに関する情報を要求するテストにも役立ちます。
多くの場合(常にではありません!)、 GlobalKeys
グローバル変数に少し似ています。 多くの場合、これらはInheritedWidgets
またはReduxのようなもの、またはBLoCテンプレートに置き換えることができます。
簡単な結論
一般に、ウィジェットのサブツリー間で状態を維持する場合は、 Keys
使用します。 これは、同じタイプのウィジェットのコレクションを変更したときに最もよく起こります。 保存するウィジェットサブツリーの上部にkey
を配置し、ウィジェットに保存されているデータに基づいてkey
タイプを選択します。
おめでとうございます、これでフラッターメイジになります! ああ、マジシャンと言った? 私は、アプリケーションのソースコードを書く人のような魔術師[2]を意味しました。 ...ほとんど。
[1]ここで受け取ったTo-doアプリケーションコードを書くためのインスピレーション
https://github.com/brianegan/flutter_architecture_samples/tree/master/vanilla
[2]作者はsorcerer
という言葉を使用し、後でsourcerer
前に追加の文字を追加します