banner
MiniKano

MiniKano

This is MiniKano's blog based on Blockchain technology. 新手,请多关照~
github

Flutter Keyの使用

私たちは以前に多くのコンポーネントを使用してきました。例えば、ContainerRow、またはElevatedButtonなどの Widget は、使用頻度が比較的高いです。しかし、彼らのコンストラクタには、通常最初の属性として存在する Key があることに気づいていましたか?

Flutter では、Key はユニークなコンポーネントを識別するために使用されます。その名の通り、Key はユニークな識別子として使用されます。

私たちは以前、この Key を使用していませんでした。この場合、2 つの状況が考えられます:

  1. コンポーネントのタイプが異なる場合:例えば、ContainerSizedBoxのように、Flutter 内部ではコンポーネントのタイプによって区別できるため、この場合はkeyを使用する必要はありません。
  2. コンポーネントのタイプが同じ場合、例えば複数のContainerからなる配列のように、Flutter はコンポーネントのタイプによって複数のコンポーネントを区別できないため、この場合はKeyを使用する必要があります。

デフォルトでは、key が定義されていない場合、Flutter は自動的に key を作成せず、widget のタイプとインデックスを使用して呼び出します。

Key がないことによる問題を事例を通じて説明します:

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Widget> list = [];
  @override
  void initState() {
    super.initState();
    list = [
      const Box(color: Colors.blue),
      const Box(color: Colors.yellow),
      const Box(color: Colors.red),
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Title'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: list,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            list.shuffle(); // shuffleはこのリストの要素をランダムに並べ替えます
          });
        },
        child: const Icon(Icons.refresh),
      ),
    );
  }
}

class Box extends StatefulWidget {
  final Color color;
  const Box({Key? key, required this.color}) : super(key: key);

  @override
  State<Box> createState() => _BoxState();
}

class _BoxState extends State<Box> {
  int _count = 0;
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      color: Colors.transparent,
      child: ElevatedButton(
        style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all(widget.color)),
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: Text(
          "$_count",
          style: Theme.of(context).textTheme.headlineLarge,
        ),
      ),
    );
  }
}

上記は StatefulWidget の例で、floatingActionButtonをクリックすることで画面上の 3 つの Box の順序をランダムに並べ替えます。効果は以下の通りです。
(画像)

1[1].gif

見ての通り、並べ替えボタンをクリックしても、box の表面上の順序は変わりますが、内部の State、つまり数字の表示は変わりません。これは Flutter がこれらの 3 つの Box を同じものとして認識し、stateless レベルの変更しか行わないためであり、State 内のものは動かないため、この現象が生じます(説明が不十分ですが、後で補足します)。

この時、私たちは key を使用する必要があります:

Key をユニークな識別子として使用する#

Key は大きく分けて 2 つのカテゴリに分けられます:GlobalKeyLocalKey。その名の通り、Global はグローバル、Local はローカル(非グローバル)です。

// GlobalKeyは全体で共有できます
final GlobalKey _globalKey1 = GlobalKey();
final GlobalKey _globalKey2 = GlobalKey();
final GlobalKey _globalKey3 = GlobalKey();
// GlobalKeyは非常に高価であり、合理的に使用する必要があります

// LocalKey
// ValueKeyはLocalKeyです
const Box(color: Colors.blue, key: ValueKey(1)),
// ObjectKeyもLocalKeyです
const Box(color: Colors.red, key: ObjectKey(Text("key"))),
// UniqueKeyもLocalKeyで、唯一無二の存在として(ユニークなハッシュコードを生成します)
Box(color: Colors.red, key: UniqueKey()),
// PageStorageKeyは現在のページの状態を保存できます。例えば、リストのスクロール状態など
PageStorageKey()

LocalKeyGlobalKeyの違い:

  • LocalKeyは同じ親ノードを持つ比較の場合に適用されます。つまり、1 つの親ノードが複数の子ノードを管理する場合、子ノードはLocalKeyを使用するのが最適です。
  • GlobalKeyは Widget を越えて状態にアクセスでき、複数の親ノードを持つ比較の場合に適用されます。つまり、子ノードが複数の親ノードに対応する場合、例えば後で説明する横縦画面の切り替え機能では、RowColumnの間で切り替える必要があり、LocalKeyだけを使用すると、Stateの現在の状態が失われます。

以上がGlobalKeyLocalKeyの定義方法です。

では、上記の例の Box に Key を追加しましょう:

以下の例では、MediaQueryorientation属性を使用してデバイスの横縦画面状態を確認します

class MyHomePage extends StatefulWidget {
    const MyHomePage({super.key});

    @override
    State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
    // GlobalKeyは全体で共有でき、横縦画面の切り替え時にコンポーネントの状態が失われません
    final GlobalKey _globalKey1 = GlobalKey();
    final GlobalKey _globalKey2 = GlobalKey();
    final GlobalKey _globalKey3 = GlobalKey();
    List<Widget> list = [];
    @override
    void initState() {
        super.initState();
        list = [
            Box(color: Colors.blue, key: _globalKey1),
            Box(color: Colors.yellow, key: _globalKey2),
            Box(color: Colors.red, key: _globalKey3),
        ];
    }

    // localKey(ValueKey)を使用すると、状態が失われます
    // List<Widget> list = [
    //   const Box(color: Colors.blue, key: ValueKey(1)),
    //   const Box(color: Colors.yellow, key: ValueKey(2)),
    //   const Box(color: Colors.red, key: ValueKey(3)),
    // ];

    @override
    Widget build(BuildContext context) {
        // 縦画面portrait横画面landscape
        print(MediaQuery.of(context).orientation);

        return Scaffold(
            appBar: AppBar(
                title: const Text('Title'),
            ),
            body: Center(
                // 横画面では横向きに表示し、縦画面では縦向きに表示します
                // しかし、これを変更すると状態が保存されなくなります。なぜならColumnとRowのkeyやコンポーネント自体が異なるからです
                child: MediaQuery.of(context).orientation == Orientation.portrait
                ? Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: list,
                )
                : Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: list,
                ),
            ),
            floatingActionButton: FloatingActionButton(
                onPressed: () {
                    setState(() {
                        list.shuffle(); // shuffleはこのリストの要素をランダムに並べ替えます
                    });
                },
                child: const Icon(Icons.refresh),
            ),
        );
    }
}

効果は以下の通りです:
1-2[1].gif

注意:GlobalKeyの使用はコストが高いため、他の選択肢がある場合はできるだけ避けるべきです。また、同じGlobalKeyは Widget ツリー全体で一意でなければならず、重複してはいけません。

GlobalKey を使用して子要素を操作する#

GlobalKey には非常に重要な特性があります:Widget を越えて State にアクセスできることです。

例えば、子 Widget(Box)内の State を取得したい場合、次のようにします。

var boxState = _globalKey.currentState as _BoxState;

boxStateを取得した後、BoxState内のメソッドや属性を直接呼び出すことができます。

さらに、子 widget ノードを取得することもできます。

var boxWidget = _globalKey.currentWidget as Box;

これにより、box 内のさまざまな属性、例えば背景色、幅、高さ、フォントサイズなどを取得できます。

完全なデモ:

// 親widget
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // GlobalKeyは全体で共有できます
  final GlobalKey _globalKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Title'),
      ),
      body: Center(
        child: Box(color: Colors.blue, key: _globalKey),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 親コンポーネントが子要素のstatewidgetの属性を取得します
          var boxState = _globalKey.currentState as _BoxState;
          setState(() {
            boxState._count++;
          });
          print(boxState._count);
          // 子Widgetのメソッドを呼び出します
          boxState.run();

          // 子widgetを取得します
          var boxWidget = _globalKey.currentWidget as Box;
          print(boxWidget.color); // MaterialColor(primary value: Color(0xff2196f3))

          // 子コンポーネントがレンダリングした属性を取得します
          var renderBox =
              _globalKey.currentContext!.findRenderObject() as RenderBox;

          print(renderBox.size); // Size(100.0, 100.0)
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

// 子widget
class Box extends StatefulWidget {
  final Color color;
  const Box({Key? key, required this.color}) : super(key: key);

  @override
  State<Box> createState() => _BoxState();
}

class _BoxState extends State<Box> {
  int _count = 0;
  void run() {
    print("私はboxのRunメソッドです");
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      color: Colors.transparent,
      child: ElevatedButton(
        style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all(widget.color)),
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: Text(
          "$_count",
          style: Theme.of(context).textTheme.headlineLarge,
        ),
      ),
    );
  }
}
読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。