[翻訳] A Tour of the Flutter Widget Framework

https://flutter.io/widgets-intro/ のほぼGoogle翻訳ベース日本語訳です。記事ではFlutterでUIを構築するベースとなるWidgetについて解説しており、React/Fluxインスパイアな印象で実装しやすそうです :sparkles: 

# Introduction

Flutter Widgetは、Reactからインスピレーションを受けた関数型リアクティブフレームワークを使用して構築されています。 主なアイデアは、UIをWidgetから構築することです。 Widgetは、現在の設定と状態を考慮して、ビューの外観を記述します。 Widgetの状態が変わると、Widgetは描画を再構築します。ある状態から次の状態に遷移する際の内部的なレンダリングツリーに加えられる変更を最小にするため、フレームワークは一つ前の描画との差分を計算します。

# Hello World

最小のFlutterアプリケーションはWidgetを一つ引数に取るrunApp関数を呼び出すものです：

```dart
import 'package:flutter/material.dart';

void main() {
  runApp(new Center(child: new Text('Hello, world!')));
}
```

<img width="239" alt="スクリーンショット 2017-01-29 16.50.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/59312/2ed759b8-34e5-9679-10db-629a38e40ac4.png">


runApp関数は与えられたWidgetをWidgetツリーのルートにします。 この例では、Widgetツリーは2つのWidget、つまり `Center` Widgetとその子である `Text` Widgetで構成されています。 フレームワークは、ルートWidgetに画面を覆わせるよう強制します。これは、テキスト「Hello、world」が画面の中央に表示されることを意味します。

アプリケーションの作成時には、Widgetが状態を管理するかどうかによって `StatelessWidget` または `StatefulWidget` のサブクラスである新しいWidgetを作成します。 Widgetの主な仕事は、`build`関数を実装することです。`build`関数は、Widgetを配下に含むWidgetの集まりとして記述します。 フレームワークは、そのプロセスが基礎となるレンダーオブジェクトを表すWidget内で終わるまで、それらのWidgetを順番に構築します。

# Basic widgets

> Main article: [Widgets Overview - Layout Models](https://flutter.io/widgets/#layout-models)

Flutterには強力な基本Widgetが付属していますが、その中で次のものがよく使用されています：

- `Text`： `Text` Widgetを使用すると、アプリケーション内でスタイル付きのテキストを作成できます。

- `Row`, `Column`： これらのフレックスWidgetを使用すると、水平方向（Row）と垂直方向（Column）の両方で柔軟なレイアウトを作成できます。そのデザインはウェブのフレックスボックスレイアウトモデルに基づいています。

- `Stack`： `Stack` Widgetを使用すると、直線的に（水平方向または垂直方向に）指向させるのではなく、Widgetをペイント順にスタックすることができます。スタックの子に `Positioned` Widgetを使用すると、スタックの上端、右端、下端、または左端を基準にして配置できます。スタックは、Webの絶対配置レイアウトモデルに基づいています。

- `Container` ： `Container` Widgetでは、四角形のビジュアルエレメントを作成できます。Containerは、背景、罫線、または影などの `BoxDecoration` で飾ることができます。Containerは、マージン、パディング、およびサイズに制約を適用することもできます。さらに、Containerは、行列を使用して3次元空間で変換することができます。

以下は、これらのWidgetと他のWidgetを組み合わせた単純なWidgetの例です：

```dart
import 'package:flutter/material.dart';

class MyAppBar extends StatelessWidget {
  // 訳追記 Dartの名前付きコンストラクタ記法と、クラスフィールドへのアサインを省略する記法の組み合わせ
  // new MyAppBar(title: foo)
  MyAppBar({this.title});

  // ウィジェットサブクラスのフィールドは常にfinalとマークされます
  final Widget title;

  @override
  Widget build(BuildContext context) {
    return new Container(
      height: 56.0, // in logical pixels
      padding: const EdgeInsets.symmetric(horizontal: 8.0),
      decoration: new BoxDecoration(backgroundColor: Colors.blue[500]),
      // Row is a horizontal, linear layout.
      child: new Row(
        // <Widget> is the type of items in the list.
        children: <Widget>[
          new IconButton(
            icon: new Icon(Icons.menu),
            tooltip: 'Navigation menu',
            onPressed: null, // null disables the button
          ),
          // Expanded expands its child to fill the available space.
          new Expanded(
            child: title,
          ),
          new IconButton(
            icon: new Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
    );
  }
}

class MyScaffold extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Material is a conceptual piece of paper on which the UI appears.
    return new Material(
      // Column is a vertical, linear layout.
      child: new Column(
        children: <Widget>[
          new MyAppBar(
            title: new Text(
              'Example title',
              style: Theme.of(context).primaryTextTheme.title,
            ),
          ),
          new Expanded(
            child: new Center(
              child: new Text('Hello, world!'),
            ),
          )
        ],
      ),
    );
  }
}

void main() {
  runApp(new MaterialApp(
    title: 'My app', // used by the OS task switcher
    home: new MyScaffold(),
  ));
}
```

<img width="240" alt="スクリーンショット 2017-01-29 16.58.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/59312/c5b267cf-b781-fe49-6a0d-a3ba6f12f4e9.png">

pubspec.yamlファイルのflutterセクションにuses-material-design：trueエントリがあることを確認してください。 これにより、定義済みのマテリアルアイコンのセットを使用することができます。

```yaml
name: my_app
flutter:
  uses-material-design: true
```

多くのWidgetは、テーマデータを継承し表示を適切にするために `MaterialApp` 関数でラップする必要があります。したがって、 `MaterialApp` を使用してアプリケーションを実行します。

`MyAppBarWidget` は、デバイスに依存しない56ピクセルの高さを持ち、左右両方に内部パディングが8ピクセルの `Container` を作成します。`Container` の内部では、 `MyAppBar` は `Row` レイアウトを使用して子を整理します。真ん中のchild、タイトルWidgetは `Expanded` とマークされます。つまり、他の子が消費していない残りの空き領域を埋めるように展開されます。複数の `Expanded` 子を持つことができ、 `flex` 引数を `Expanded` にとすることで使用可能な領域を消費する割合を決定できます。

`MyScaffoldWidget` は、その子を縦の列に編成します。列の最上部では、 `MyAppBar` のインスタンスを配置し、アプリケーションバーにテキストWidgetを渡してタイトルとして使用します。Widgetを他のWidgetに引数として渡すことは、さまざまな方法で再利用できる汎用Widgetを作成する強力な手法です。最後に、 `MyScaffold` は、 `Expanded` を使用して、残りのスペースを本体に埋め込みます。本体は、中央に配置されたメッセージで構成されています。

# Using material design

> Main article: [Widgets Overview - Material Design Widgets](https://flutter.io/widgets/#material-design-widgets)

Flutterには、Material Designに従ったアプリケーションを構築するのに役立つ多数のWidgetが用意されています。 マテリアルデザインアプリは `MaterialApp` Widgetで始まります。アプリケーションのルートには、文字列で識別されたWidgetのスタック（「ルート」とも呼ばれます）を管理するナビゲータを含む多くの有用なWidgetが構築されています。 ナビゲータを使用すると、アプリケーションの画面間をスムーズに移行できます。 `MaterialApp` Widgetの使用は完全にオプションですが、良い方法です。

```dart
import 'package:flutter/material.dart';

void main() {
  runApp(new MaterialApp(
    title: 'Flutter Tutorial',
    home: new TutorialHome(),
  ));
}

class TutorialHome extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Scafold is a layout for the major material design widgets.
    return new Scaffold(
      appBar: new AppBar(
        leading: new IconButton(
          icon: new Icon(Icons.menu),
          tooltip: 'Navigation menu',
          onPressed: null,
        ),
        title: new Text('Example title'),
        actions: <Widget>[
          new IconButton(
            icon: new Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
      // body is the majority of the screen.
      body: new Center(
        child: new Text('Hello, world!'),
      ),
      floatingActionButton: new FloatingActionButton(
        tooltip: 'Add', // used by assistive technologies
        child: new Icon(Icons.add),
        onPressed: null,
      ),
    );
  }
}
```

<img width="239" alt="スクリーンショット 2017-01-29 17.24.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/59312/96ed472f-052b-3172-762b-5b2a08851e11.png">

`MyAppBar` と `MyScaffold` をmaterial.dartの `AppBar` と `Scaffold` Widgetに切り替えたので、私たちのアプリはMaterial Designのように少し見えるようになりました。 たとえば、アプリケーションバーには影があり、タイトルテキストは正しいスタイルを自動的に継承します。 また、おまけにフローティングアクションボタンも追加しました。

他のWidgetへの引数としてWidgetを再び渡していることに注目してください。  `Scaffold` Widgetは、名前付き引数としていくつかの異なるWidgetを取ります。それぞれのWidgetは、適切な場所の `Scaffold` レイアウトに配置されています。 同様に、 `AppBar` Widgetは、 `title` Widgetの `leading` と `actions` Widgetを渡すことができます。 このパターンはフレームワーク全体で繰り返され、独自のWidgetを設計する際に考慮する可能性があります。

# Handling gestures

> Main article: [Gestures in Flutter](https://flutter.io/gestures/)

ほとんどのアプリケーションには、システムとのユーザーのインタラクションが含まれています。インタラクティブなアプリケーションを構築するための第一歩は、入力ジェスチャーを検出することです。 シンプルなボタンを作成してその動作を見てみましょう：

```dart
class MyButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new GestureDetector(
      onTap: () {
        print('MyButton was tapped!');
      },
      child: new Container(
        height: 36.0,
        padding: const EdgeInsets.all(8.0),
        margin: const EdgeInsets.symmetric(horizontal: 8.0),
        decoration: new BoxDecoration(
          borderRadius: new BorderRadius.circular(5.0),
          backgroundColor: Colors.lightGreen[500],
        ),
        child: new Center(
          child: new Text('Engage'),
        ),
      ),
    );
  }
}
```

`GestureDetectorWidget` には視覚的な表現はなく、代わりにユーザーが行ったジェスチャーが検出されます。 ユーザーが `Container` をタップすると、 `GestureDetector` は `onTap`コールバックを呼び出します。この場合、コンソールにメッセージが出力されます。  `GestureDetector` を使用して、タップ、ドラッグ、スケールなどのさまざまな入力ジェスチャを検出できます。

多くのWidgetは `GestureDetector` を使用して、他のWidgetに対してオプションのコールバックを提供します。 たとえば、 `IconButton` 、 `RaisedButton` 、および `FloatingActionButton` Widgetには、ユーザーがWidgetをタップしたときにトリガーされる `onPressed` コールバックがあります。

# Changing widgets in response to input

> Main articles: [StatefulWidget](https://docs.flutter.io/flutter/widgets/StatefulWidget-class.html), [State.setState](https://docs.flutter.io/flutter/widgets/State/setState.html)

ここまでは、ステートレスなWidgetしか使用していませんでした。 ステートレスWidgetは、親Widgetから引数を受け取り、最終的なメンバー変数に格納します。 Widgetの `build` 関数が呼ばれると、格納されたこれらの値を使用して、作成されたWidgetの新しい引数が生成されます。

たとえば、より興味深い方法でユーザの入力に反応するなど、より複雑なエクスペリエンスを構築するために、アプリケーションは通常、ある状態を保持します。 Flutterは `StatefulWidgets` を使用してこのアイデアを実行します。  `StatefulWidgets` はStateオブジェクトを生成する方法を知っている特別なWidgetで、Stateオブジェクトを保持するために使用されます。 前述の `RaisedButton` を使用して、この基本的な例を考えてみましょう。

```dart
class Counter extends StatefulWidget {
  // このクラスは状態の設定を行います。 それは親から渡された値を保持し（この場合は何もない）
  // Stateのbuildメソッドで使われます。 Widgetサブクラスのフィールドは、
  // 常に「final」とマークされます。

  // 訳追加: Dartではアンダースコアで始まるフィールドやクラスなどはprivateとして扱われます
  @override
  _CounterState createState() => new _CounterState();
}

class _CounterState extends State<Counter> {
  int _counter = 0;

  void _increment() {
    setState(() {
      // setStateを呼ぶとFlutterフレームワークにこのStateで値の変化が起こった
      // ことを知らせます。それによりbuildメソッドが再度実行される可能性があり、
      // それにより更新された値を表示に反映することができます。
      // setState（）を呼び出さずに_counterを変更した場合、
      // buildメソッドは再び呼び出されないため、何も起こらないように見えます。
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // このメソッドは、上記の_incrementCounterメソッドのように、
    // setStateが呼び出されるたびに再実行されます。 
    // Flutterフレームワークは、ビルドメソッドの再実行を高速化するように
    // 最適化されているため、Widgetのインスタンスを個別に変更する必要がなく、
    // 更新が必要なものだけを再構築できます。
    return new Row(
      children: <Widget>[
        new RaisedButton(
          onPressed: _increment,
          child: new Text('Increment'),
        ),
        new Text('Count: $_counter'),
      ],
    );
  }
}
```

あなたはなぜStatefulWidgetとStateが別々のオブジェクトであるのか不思議に思うかもしれません。 Flutterでは、これらの2つのタイプのオブジェクトは異なるライフサイクルを持ちます。 Widgetは一時オブジェクトであり、現在の状態でアプリケーションのプレゼンテーションを構築するために使用されます。 一方、Stateオブジェクトは、 `build()` の呼び出しの間、永続的であり、情報を覚えておくことができます。

上記の例では、ユーザーの入力を受け入れ、ビルドメソッドで直接結果を使用しています。 より複雑なアプリケーションでは、Widget階層のさまざまな部分が異なる懸念を引き起こす可能性があります。 たとえば、あるWidgetは、日付や場所などの特定の情報を収集する目的で複雑なユーザーインターフェースを提示し、別のWidgetはその情報を使用してプレゼンテーション全体を変更することがあります。

Flutterでは、変更通知は、現在の状態がプレゼンテーションを行うステートレスWidgetに「ダウン」しながら、コールバックを使用してWidget階層を「上」にフローさせます。 このフローをリダイレクトする共通の親はStateです。 このやや複雑な例を使って、実際の動作を見てみましょう：

```dart
class CounterDisplay extends StatelessWidget {
  CounterDisplay({this.count});

  final int count;

  @override
  Widget build(BuildContext context) {
    return new Text('Count: $count');
  }
}

class CounterIncrementor extends StatelessWidget {
  CounterIncrementor({this.onPressed});

  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return new RaisedButton(
      onPressed: onPressed,
      child: new Text('Increment'),
    );
  }
}

class Counter extends StatefulWidget {
  @override
  _CounterState createState() => new _CounterState();
}

class _CounterState extends State<Counter> {
  int _counter = 0;

  void _increment() {
    setState(() {
      ++_counter;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Row(children: <Widget>[
      new CounterIncrementor(onPressed: _increment),
      new CounterDisplay(count: _counter),
    ]);
  }
}
```

カウンタの表示（CounterDisplay）とカウンタの変更（CounterIncrementor）の関心を明確に分離して、2つの新しいステートレスWidgetをどのように作成したかに注目してください。 結果は前の例と同じですが、責任の分離により、親の単純さを維持しながら個々のWidgetに複雑さをカプセル化することができます。

# Bringing it all together

上で紹介した概念をまとめたより完全な例を考えてみましょう。 様々な製品を表示し、商品をショッピングカートに入れることができる架空のショッピングアプリケーションを作ってみます。 プレゼンテーションクラス `ShoppingListItem` を定義することから始めましょう：

```dart
class Product {
  const Product({this.name});
  final String name;
}

// 訳注: CartChangedCallbackという名前で(Product, bool) => voidな型を定義
typedef void CartChangedCallback(Product product, bool inCart);

class ShoppingListItem extends StatelessWidget {
  ShoppingListItem({Product product, this.inCart, this.onCartChanged})
      : product = product,
        super(key: new ObjectKey(product));

  final Product product;
  final bool inCart;
  final CartChangedCallback onCartChanged;

  Color _getColor(BuildContext context) {
    // テーマは、ツリーのさまざまな部分が異なるテーマを持つことができるため、
    // BuildContextに依存します。 BuildContextはビルドがどこで行われているか、
    // したがってどのテーマを使用するかを示します。

    return inCart ? Colors.black54 : Theme.of(context).primaryColor;
  }

  TextStyle _getTextStyle(BuildContext context) {
    if (!inCart) return null;

    return new TextStyle(
      color: Colors.black54,
      decoration: TextDecoration.lineThrough,
    );
  }

  @override
  Widget build(BuildContext context) {
    return new ListItem(
      onTap: () {
        onCartChanged(product, !inCart);
      },
      leading: new CircleAvatar(
        backgroundColor: _getColor(context),
        child: new Text(product.name[0]),
      ),
      title: new Text(product.name, style: _getTextStyle(context)),
    );
  }
}
```

`ShoppingListItemWidget` は、ステートレスWidgetの共通パターンに従います。 コンストラクタで受け取った値を `final` メンバ変数に格納し、 `build` 関数で使用します。 たとえば、現在のテーマのプライマリカラーとグレーを使用する別の視覚アピアランスを `inCart` ブール値を使って切り替えます。

ユーザーがリストアイテムをタップすると、Widgetはその `inCart` 値を直接変更しません。 代わりに、Widgetは親Widgetから受け取った `onCartChanged` 関数を呼び出します。 このパターンを使用すると、状態をWidget階層の上位に格納できます。これにより、状態が長期間維持されます。 極端な場合、 `runApp` に渡されたWidgetに格納されている状態は、アプリケーションの存続期間中存続します。

親が `onCartChanged` コールバックを受け取ると、親はその内部状態を更新し、親が新しい `inCart` 値を持つ `ShoppingListItem` の新しいインスタンスを再構築して作成するようにします。 親は再構築時に `ShoppingListItem` の新しいインスタンスを作成しますが、フレームワークは新しく構築されたWidgetと以前に構築されたWidgetを比較し、その違いのみを基礎となるレンダーオブジェクトに適用します。

mutableな状態を格納する親Widgetの例を見てみましょう：

```dart
class ShoppingList extends StatefulWidget {
  ShoppingList({Key key, this.products}) : super(key: key);

  final List<Product> products;

  // フレームワークは、Widgetがツリーの所定の場所に初めて表示されたときに
  // createStateを呼び出します。 親が同じタイプのWidget（同じKeyを持つ）を
  // 再構築して使用する場合、フレームワークは新しいStateオブジェクトを作成する代わりに
  // Stateオブジェクトを再利用します。

  @override
  _ShoppingListState createState() => new _ShoppingListState();
}

class _ShoppingListState extends State<ShoppingList> {
  Set<Product> _shoppingCart = new Set<Product>();

  void _handleCartChanged(Product product, bool inCart) {
    setState(() {
      // ユーザーがカート内のものを変更するときは、setState呼び出しの中で
      // _shoppingCartを変更して再構築をトリガーする必要があります。
      // フレームワークは次に配下Widgetのbuild関数を呼び出して、
      // アプリケーションの表示を更新します。

      if (inCart)
        _shoppingCart.add(product);
      else
        _shoppingCart.remove(product);
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Shopping List'),
      ),
      body: new MaterialList(
        type: MaterialListType.oneLineWithAvatar,
        children: config.products.map((Product product) {
          return new ShoppingListItem(
            product: product,
            inCart: _shoppingCart.contains(product),
            onCartChanged: _handleCartChanged,
          );
        }),
      ),
    );
  }
}

final List<Product> _kProducts = <Product>[
  new Product(name: 'Eggs'),
  new Product(name: 'Flour'),
  new Product(name: 'Chocolate chips'),
];

void main() {
  runApp(new MaterialApp(
    title: 'Shopping List',
    home: new ShoppingList(products: _kProducts),
  ));
}
```

<img width="237" alt="スクリーンショット 2017-01-29 18.28.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/59312/e3072239-7eae-09dd-3728-83aabaf51107.png">


`ShoppingList` クラスは `StatefulWidget` を継承します。つまり、このWidgetは可変状態を格納します。 `ShoppingListWidget` が最初にツリーに挿入されると、フレームワークは `createState` 関数を呼び出して `_ShoppingListState` の新しいインスタンスを作成し、ツリー内のその位置に関連付けます。 （一般的にStateのサブクラスには、プライベートな実装の詳細であることを示すために、名前の先頭にアンダースコアを付けることに注意してください）。このWidgetの親が再構築されると、親は `ShoppingList` の新しいインスタンスを作成しますが、フレームワークは `createState` を再度呼び出すのではなく、すでにツリー内にある `_ShoppingListState` インスタンスを再利用します。

現在の `ShoppingList` のプロパティにアクセスするには、 `_ShoppingListState` はその `config` プロパティを使用できます。 親が再構築して新しい `ShoppingList` を作成する場合、 `_ShoppingListState` も新しい `config` の値で再構築されます。 `config` プロパティが変更されたときに通知を受けたい場合は、 `oldConfig` に渡される `didUpdateConfig` 関数をオーバーライドして、古い `config` と現在の `config` を比較させることができます。

 `onCartChanged` コールバックを処理するとき、 `_ShoppingListState` は、 `_shoppingCart` から商品を追加または削除することによって、内部状態を変更します。 フレームワークが内部状態を変更することを通知するために、これらの呼び出しを `setState` 呼び出しでラップします。 `setState` を呼び出すと、このWidgetはダーティとマークされ、次にアプリが画面を更新する必要があるときに再構築されるようにスケジュールされます。 Widgetの内部状態を変更するときに `setState` を忘れた場合、フレームワークはWidgetが汚れているとは認識せず、Widgetのビルド関数を呼び出さない可能性があります。

このように状態を管理することで、子Widgetを作成および更新するためのコードを別途記述する必要はありません。 代わりに、両方の状況を処理する `build` 関数を実装するだけです。

# Responding to widget lifecycle events

> Main article: [State](https://docs.flutter.io/flutter/widgets/State-class.html)

StatefulWidgetで `createState` を呼び出した後、フレームワークは新しい状態オブジェクトをツリーに挿入し、状態オブジェクトに対して `initState` を呼び出します。 `State` のサブクラスは、 `initState` をオーバーライドして、1回だけ実行する必要のある作業を行うことができます。 たとえば、 `initState` をオーバーライドしてアニメーションを設定したり、プラットフォームサービスに登録することができます。 `initState` の実装は、 `super.initState`を呼び出して開始する必要があります。

状態オブジェクトがもはや必要でなくなると、フレームワークは状態オブジェクトに対して `dispose` を呼び出します。 `dispose` 関数をオーバーライドしてクリーンアップ作業を行うことができます。 たとえば、 `dispose` をオーバーライドしてタイマーをキャンセルしたり、プラットフォームサービスの unsbscribe することができます。 `dispose` の実装は、通常、 `super.dispose` を呼び出すことで終了します。

# Keys

> Main article: [Key](https://docs.flutter.io/flutter/widgets/Key-class.html)

Widgetの再構築時にキーを使用して、フレームワークがどの他のWidgetと一致するかを制御することができます。 デフォルトでは、フレームワークは `runtimeType` と表示される順序に従って、現在のビルドと前のビルドのWidgetを一致させます。 キーでは、フレームワークは2つのWidgetが同じキーと同じ `runtimeType` を持つことを要求します。

キーは、同じ種類のWidgetのインスタンスを多数作成するWidgetで最も便利です。 たとえば、 `ShoppingListItem` インスタンスを構築して可視領域を埋める `ShoppingListWidget` は、次のようになります。

- キーがなければ、現在のビルドの最初のエントリは、意味的にリストの最初のエントリが画面からスクロールしてもはやビューポートに表示されなくても、前のビルドの最初のエントリと常に同期します。
- リスト内の各エントリに「セマンティック」キーを割り当てることにより、フレームワークは一致するセマンティックキー、したがって同様の（または同一の）視覚的外観を持つエントリを同期させるので、無限リストをより効率的にすることができる。 さらに、エントリを意味的に同期するということは、ステートフルな子Widgetに保持されている状態が、ビューポート内の同じ数値位置のエントリではなく、同じセマンティックエントリに関連付けられたままであることを意味します。

# Global Keys

> Main article: [GlobalKey](https://docs.flutter.io/flutter/widgets/GlobalKey-class.html)

グローバルキーを使用して、子Widgetを一意に識別できます。 グローバルキーは、兄弟間で一意である必要のあるローカルキーとは異なり、Widget階層全体でグローバルに一意でなければなりません。 グローバルに一意であるため、グローバルキーを使用してWidgetに関連付けられた状態を取得できます。