Flutter学習6日目 -公式を読む 7. ウィジェットの紹介-

**※自分の学習記録のメモとしてこの記事を書いています。**

1日目：[Flutter学習1日目 -公式を読む 1. インストール編-](https://qiita.com/umiremix/items/940f38e03be4b71dc33d)
2日目前編：[Flutter学習2日目前編 -公式を読む 2. エディターの設定編-](https://qiita.com/umiremix/items/0ff6600a6a9f996a3a74)
2日目後編：[Flutter学習2日目後編 -公式を読む 3. テストドライブ編-](https://qiita.com/umiremix/items/f1a84f96199054fbebf2)
3日目:[Flutter学習3日目 -公式を読む 4. スターターFlutterアプリを作成する-](https://qiita.com/umiremix/items/796cc8a7520629cae3b1)
4日目:[Flutter学習4日目 -公式を読む 5. インタラクティブ機能を実装-](https://qiita.com/umiremix/items/abf0a5d4005224cdb083)
5日目：[Flutter学習5日目 -公式を読む 6. Web開発者のためのFlutter-](https://qiita.com/umiremix/items/ed9e61cd4e4c17ed1bdf)
6日目：Flutter学習6日目 -公式を読む 7. ウィジェットの紹介-（イマココ）

日が空いてしまいましたが挫けずに今週も更新していきます…。😌
今日は公式の[Introduction to widgets
](https://flutter.dev/docs/development/ui/widgets-intro)ページを読み進めます！

## はじめに

Flutterウィジェットは、Reactからインスピレーションを得た最新のフレームワークを使用して構築されています。ウィジェットからUIを構築することが主な考え方です。ウィジェットは現在の構成と状態が与えられた場合のビューの外観を記述します。ウィジェットの状態が変化すると、ウィジェットはその記述を再構築します。フレームワークは、ある状態から次の状態に移行するために必要な、基礎となるレンダーツリーの最小の変更を決定するために、以前の記述と比較して、その記述を再構築します。

## Hello world

最小限のFlutterアプリ `runApp()` はウィジェットで関数を呼び出すだけです。

```dart

import 'package:flutter/material.dart';

void main() {
  runApp(
    Center(
      child: Text(
        'Hello, world!',
        textDirection: TextDirection.ltr,
      ),
    ),
  );
}
```

`runApp()` 関数は、与えられたウィジェットを受け取り、それをウィジェットツリーのルートにします。この例では、ウィジェットツリーは、 `Center` ウィジェットとその子ウィジェットである `Text` ウィジェットの2つのウィジェットで構成されています。フレームワークは、ルートウィジェットが画面をカバーするように強制的に設定します。この例では、テキストの方向を指定する必要があります。 `MaterialApp` ウィジェットを使用する場合は、後ほど説明するように、この処理を行います。

アプリを書くときには、ウィジェットがステートを管理するかどうかに応じて、 `StatelessWidget` または `StatefulWidget` のサブクラスである新しいウィジェットを作成するのが一般的です。ウィジェットの主な仕事は `build()` 関数を実装することです。フレームワークは、ウィジェットのジオメトリを計算して記述する  `RenderObject` を表すウィジェットでプロセスがボトムアウトするまで、これらのウィジェットを順番にビルドしていきます。

<img width="529" alt="スクリーンショット 2020-08-06 14.52.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62182/76797c85-7482-3dd9-6783-d31e76c6726e.png">

## 基本的なウィジェット

Flutterには強力な基本ウィジェットが付属していますが、その中でも以下のものがよく使われています。

**Text**
`Text` ウィジェットを使用すると、アプリケーション内でスタイル化されたテキストを作成することができます。

**Row, Column**
これらのフレックスウィジェットを使用すると、水平方向（Row）と垂直方向（Column）の両方の方向に柔軟なレイアウトを作成できます。これらのオブジェクトの設計は、Webのフレックスボックスレイアウトモデルに基づいています。

**Stack**
直線的な方向 (水平または垂直) の代わりに、`Stack` ウィジェットでは、ペイント順にウィジェットを重ねて配置することができます。そして、`Stack` の子ウィジェットで `Positioned` ウィジェットを使用して、スタックの上、右、下、左端からの相対的な位置に配置することができます。`Stack` はWebの絶対配置（absolute）レイアウトモデルに基づいています。

**Container**
`Container` ウィジェットを使用すると、長方形のビジュアル要素を作成することができます。コンテナは背景、ボーダー、影などの `BoxDecoration` で装飾できます。Containerは、そのサイズに適用されるマージン、パディング、および制約を設定することもできます。さらに、Containerはmatrixを使用して3次元空間で変換できます。

以下は、これらのウィジェットと他のウィジェットを組み合わせたいくつかのシンプルなウィジェットです。

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

class MyAppBar extends StatelessWidget {
  MyAppBar({this.title});

  // Fields in a Widget subclass are always marked "final".

  final Widget title;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 56.0, // in logical pixels
      padding: const EdgeInsets.symmetric(horizontal: 8.0),
      decoration: BoxDecoration(color: Colors.blue[500]),
      // Row is a horizontal, linear layout.
      child: Row(
        // <Widget> is the type of items in the list.
        children: <Widget>[
          IconButton(
            icon: Icon(Icons.menu),
            tooltip: 'Navigation menu',
            onPressed: null, // null disables the button
          ),
          // Expanded expands its child to fill the available space.
          Expanded(
            child: title,
          ),
          IconButton(
            icon: 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 Material(
      // Column is a vertical, linear layout.
      child: Column(
        children: <Widget>[
          MyAppBar(
            title: Text(
              'Example title',
              style: Theme.of(context).primaryTextTheme.headline6,
            ),
          ),
          Expanded(
            child: Center(
              child: Text('Hello, world!'),
            ),
          ),
        ],
      ),
    );
  }
}

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

```

<img width="529" alt="スクリーンショット 2020-08-06 15.07.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62182/2497ed6b-1f3d-547f-c570-a8865dde294a.png">

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

```yaml

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

多くの Material Design ウィジェットは、テーマデータを継承するために、正しく表示するために `MaterialApp` の内部にある必要があります。そのため、 `MaterialApp` でアプリケーションを実行します。

`MyAppBar` ウィジェットは、デバイスに依存しない 56 ピクセルの高さの `Container` を作成し、内部のパディングは左右とも 8 ピクセルです。コンテナ内では、 `MyAppBar` は `Row` レイアウトを使用して子を整理します。真ん中の子である `title` ウィジェットは、 `Expanded` とマークされています。複数の `Expanded` の子を持つことができ、 `Expanded` への `flex` 引数を使用して、使用可能なスペースを消費する割合を決定することができます。

`MyScaffold` ウィジェットは、その子を縦の列に整理します。列の上部に `MyAppBar` のインスタンスを配置し、アプリバーにタイトルとして使用する `Text` ウィジェットを渡します。他のウィジェットへの引数としてウィジェットを渡すことは、様々な方法で再利用できる汎用ウィジェットを作成することができる強力なテクニックです。最後に、 `MyScaffold` は、 `Expanded` を使用して、残りのスペースを中央に配置されたメッセージで構成されるボディで埋めます。

## マテリアルコンポーネントの使用

Flutter には、Material Design に従ったアプリを構築するのに役立つ多くのウィジェットが用意されています。`MaterialApp` ウィジェットは、「ルート」とも呼ばれる文字列で識別されたウィジェットのスタックを管理する `Navigator` など、多くの便利なウィジェットをアプリのルートに構築します。 `Navigator` を使用すると、アプリケーションの画面間をスムーズに遷移できます。 `MaterialApp` ウィジェットの使用は完全に任意ですが、良い練習になります。

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

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

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

<img width="529" alt="スクリーンショット 2020-08-06 20.26.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62182/a26af19b-f233-8952-ede2-7e0e1f9e0756.png">

コードが `MyAppBar` と `MyScaffold` から `AppBar` と` Scaffold` ウィジェットに切り替わった今、 `material.dart` からアプリが少しだけMaterialに見えてきました。例えば、アプリバーには影がついていて、タイトルテキストは自動的に正しいスタイリングを継承するようになっています。フローティングアクションボタンも追加されました。

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

## ジェスチャーの処理

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

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

<img width="529" alt="スクリーンショット 2020-08-06 21.07.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62182/f16a61ff-3b8d-e1b2-b91c-34392b5e8831.png">

<img width="697" alt="スクリーンショット 2020-08-06 21.08.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62182/4e52a1c7-0538-6282-3c04-cb1045d584a0.png">


`GestureDetector` ウィジェットは視覚的な表現を持たず、代わりにユーザによるジェスチャーを検出します。ユーザがコンテナをタップすると、`GestureDetector` は `onTap()` コールバックを呼び出します。`GestureDetector` を使用して、タップ、ドラッグ、スケールなど、さまざまな入力ジェスチャを検出することができます。

多くのウィジェットでは、`GestureDetector` を使用して他のウィジェットのオプションのコールバックを提供しています。たとえば、`IconButton`、`RaisedButton`、`FloatingActionButton` ウィジェットには、ユーザがウィジェットをタップしたときにトリガーされる `onPressed()` コールバックがあります。

## 入力に応じてウィジェットを変更する

これまでのところ、このページではステートレス・ウィジェットのみを使用しています。ステートレス・ウィジェットは親ウィジェットから引数を受け取り、最終的なメンバ変数に格納します。ウィジェットが `build()` を要求されたとき、ウィジェットはこれらの保存された値を使用して、作成するウィジェットの新しい引数を導出します。

より複雑な体験を構築するために、例えば、ユーザーの入力に対してより面白い反応をするために、アプリケーションは通常、何らかの状態を保持します。Flutter は、この考えを実現するために `StatefulWidgets` を使用しています。 `StatefulWidgets` は、ステートオブジェクトを生成する方法を知っている特別なウィジェットで、ステートを保持するために使用されます。先ほどの `RaisedButton` を使った基本的な例を考えてみましょう。

```dart
class Counter extends StatefulWidget {
  // This class is the configuration for the state. It holds the
  // values (in this case nothing) provided by the parent and used
  // by the build  method of the State. Fields in a Widget
  // subclass are always marked "final".

  @override
  _CounterState createState() => _CounterState();
}

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

  void _increment() {
    setState(() {
      // This call to setState tells the Flutter framework that
      // something has changed in this State, which causes it to rerun
      // the build method below so that the display can reflect the
      // updated values. If you change _counter without calling
      // setState(), then the build method won't be called again,
      // and so nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called,
    // for instance, as done by the _increment method above.
    // The Flutter framework has been optimized to make rerunning
    // build methods fast, so that you can just rebuild anything that
    // needs updating rather than having to individually change
    // instances of widgets.
    return Row(
      children: <Widget>[
        RaisedButton(
          onPressed: _increment,
          child: Text('Increment'),
        ),
        Text('Count: $_counter'),
      ],
    );
  }
}
```

<img width="529" alt="スクリーンショット 2020-08-06 21.21.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62182/3d5d6131-2501-5c73-cf65-6a545424c34d.png">

なぜ `StatefulWidget` と `State` が別のオブジェクトなのか不思議に思うかもしれません。Flutter では、この2種類のオブジェクトは異なるライフサイクルを持っています。ウィジェットは一時的なオブジェクトで、現在の状態でアプリケーションのプレゼンテーションを構築するために使用されます。一方、ステートオブジェクトは `build()` の呼び出しの間に永続的に保持され、情報を記憶することができます。

上の例では、ユーザの入力を受け付け、その結果を直接 `build()` メソッドで使用しています。例えば、あるウィジェットは日付や場所などの特定の情報を収集する目的で複雑なユーザーインターフェースを表示し、別のウィジェットはその情報を使って全体の表示を変更することができます。

Flutter では、変更通知はコールバックによってウィジェット階層の「上」に流れ、現在の状態はプレゼンテーションを行うステートレスウィジェットに「下」に流れます。このフローをリダイレクトする共通の親は `State` です。以下の少し複雑な例は、実際にどのように動作するかを示しています。

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

  final int count;

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

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

  final VoidCallback onPressed;

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

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

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

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

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

2つの新しいステートレス・ウィジェットが作成され、カウンタの表示(`CounterDisplay`)とカウンタの変更(`CounterIncrementor`)がきれいに分離されていることに注目してください。結果は前の例と同じですが、責任を分離することで、親ウィジェットのシンプルさを維持しつつ、より複雑さを個々のウィジェットにカプセル化することができます。

## すべてをまとめる

以下に、これらの概念をまとめたより完全な例を示します。仮定のショッピング・アプリケーションは、販売のために提供される様々な商品を表示し、購入を目的としたショッピング・カートを維持します。まず、プレゼンテーション・クラスである `ShoppingListItem` を定義します。
 
```dart

class Counter extends StatefulWidget {
  // This class is the configuration for the state. It holds the
  // values (in this case nothing) provided by the parent and used
  // by the build  method of the State. Fields in a Widget
  // subclass are always marked "final".

  @override
  _CounterState createState() => _CounterState();
}

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

  void _increment() {
    setState(() {
      // This call to setState tells the Flutter framework that
      // something has changed in this State, which causes it to rerun
      // the build method below so that the display can reflect the
      // updated values. If you change _counter without calling
      // setState(), then the build method won't be called again,
      // and so nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called,
    // for instance, as done by the _increment method above.
    // The Flutter framework has been optimized to make rerunning
    // build methods fast, so that you can just rebuild anything that
    // needs updating rather than having to individually change
    // instances of widgets.
    return Row(
      children: <Widget>[
        RaisedButton(
          onPressed: _increment,
          child: Text('Increment'),
        ),
        Text('Count: $_counter'),
      ],
    );
  }
}
```

`ShoppingListItem` ウィジェットは、ステートレス・ウィジェットの一般的なパターンに従います。コンストラクタで受け取った値を最終的なメンバ変数に格納し、それを `build()` 関数の間に使用します。例えば、 `inCart` booleanは、現在のテーマの原色を使用するものと、グレーを使用するものの2つの外観を切り替えます。

ユーザがリストアイテムをタップしたとき、ウィジェットは直接 `inCart` の値を変更しません。その代わりに、ウィジェットは親ウィジェットから受け取った `onCartChanged` 関数を呼び出します。このパターンでは、ウィジェット階層の上位にステートを保存することができ、ステートはより長い期間持続します。極端に言えば、 `runApp()` に渡されたウィジェットに保存された状態は、アプリケーションの寿命まで持続します。

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

ここでは、可変な状態を保存する親ウィジェットの例を示します。

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

  final List<Product> products;

  // The framework calls createState the first time a widget
  // appears at a given location in the tree.
  // If the parent rebuilds and uses the same type of
  // widget (with the same key), the framework re-uses the State object
  // instead of creating a new State object.

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

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

  void _handleCartChanged(Product product, bool inCart) {
    setState(() {
      // When a user changes what's in the cart, you need to change
      // _shoppingCart inside a setState call to trigger a rebuild.
      // The framework then calls build, below,
      // which updates the visual appearance of the app.

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Shopping List'),
      ),
      body: ListView(
        padding: EdgeInsets.symmetric(vertical: 8.0),
        children: widget.products.map((Product product) {
          return ShoppingListItem(
            product: product,
            inCart: _shoppingCart.contains(product),
            onCartChanged: _handleCartChanged,
          );
        }).toList(),
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(
    title: 'Shopping App',
    home: ShoppingList(
      products: <Product>[
        Product(name: 'Eggs'),
        Product(name: 'Flour'),
        Product(name: 'Chocolate chips'),
      ],
    ),
  ));
}

```

<img width="529" alt="スクリーンショット 2020-08-06 21.41.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62182/8c6ef1d8-f56b-9bf2-7a22-0522dfbc27fd.png">

`ShoppingList` クラスは `StatefulWidget` を継承しています。`ShoppingList` ウィジェットが最初にツリーに挿入されると、フレームワークは `createState()` 関数を呼び出して `_ShoppingListState` の新しいインスタンスを作成し、ツリー内のその場所に関連付けます。 (`State` のサブクラスは通常、プライベートな実装の詳細であることを示すために、先頭にアンダースコアを付けて名前が付けられていることに注意してください)。このウィジェットの親がリビルドすると、親は `ShoppingList` の新しいインスタンスを作成しますが、フレームワークは `createState` を再度呼び出すのではなく、既にツリーにある `_ShoppingListState` インスタンスを再利用します。

現在の `ShoppingList` のプロパティにアクセスするには、 `_ShoppingListState` はそのウィジェット・プロパティを使用できます。親が再構築して新しい `ShoppingList` を作成すると、 `_ShoppingListState` は新しいウィジェットの値で再構築します。ウィジェットのプロパティが変更されたときに通知を受けたい場合は、 `didUpdateWidget()` 関数をオーバーライドします。

`onCartChanged` コールバックを処理するとき、 `_ShoppingListState` は、 `_shoppingCart` から商品を追加または削除することで内部状態を変更します。内部状態を変更したことをフレームワークに知らせるために、 `setState()` 呼び出しでそれらの呼び出しをラップします。 `setState` を呼び出すと、このウィジェットはダーティなものとしてマークされ、次回アプリが画面を更新する必要があるときに再構築されるようにスケジュールされます。ウィジェットの内部状態を変更するときに `setState` を呼び出すのを忘れた場合、フレームワークはウィジェットがダーティであることを認識せず、ウィジェットの `build()` 関数を呼び出さない可能性があり、ユーザー インターフェイスが変更された状態を反映して更新されない可能性があります。この方法でステートを管理することで、子ウィジェットの作成と更新のために別のコードを書く必要はありません。代わりに、ビルド関数を実装するだけで、両方の状況を処理します。

## ウィジェットのライフサイクルイベントへの対応

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

ステートオブジェクトが不要になると、フレームワークはステートオブジェクトに対して `dispose()` を呼び出します。 `dispose` 関数をオーバーライドしてクリーンアップ作業を行います。例えば、タイマーをキャンセルしたり、プラットフォームサービスからの退会を解除するために `dispose` をオーバーライドします。 `dispose` の実装は、通常 `super.dispose` を呼び出して終了します。

## キー

ウィジェットのリビルド時にフレームワークが他のウィジェットとマッチするウィジェットを制御するためにキーを使います。デフォルトでは、フレームワークは現在のビルドと以前のビルドのウィジェットを `runtimeType` と表示順に従ってマッチします。キーを使うと、フレームワークは2つのウィジェットが同じ `runtimeType` と同じキーを持っていることを要求します。

キーは、同じタイプのウィジェットの多くのインスタンスをビルドするウィジェットで最も便利です。例えば、`ShoppingList` ウィジェットは、その可視領域を埋めるのに十分な数の `ShoppingListItem` インスタンスをビルドします。

キーがなければ、現在のビルドの最初のエントリは、意味的にはリストの最初のエントリが画面外にスクロールしてビューポートに表示されなくなっても、常に前のビルドの最初のエントリと同期します。

リストの各エントリに「セマンティック」キーを割り当てることで、無限リストはより効率的になります。フレームワークは、一致するセマンティックキーを持つエントリを同期するので、似たような（あるいは同じ）視覚的な外観を持つことになります。さらに、セマンティックにエントリを同期するということは、ステートフルな子ウィジェットで保持される状態は、ビューポート内の同じ数値位置にあるエントリではなく、同じセマンティックエントリにアタッチされたままであることを意味します。

## グローバルキー

グローバルキーを使用して、子ウィジェットを一意に識別します。グローバル・キーは、兄弟間でのみ一意である必要があるローカル・キーとは異なり、ウィジェット階層全体でグローバルに一意である必要があります。グローバルキーはグローバルに一意であるため、グローバルキーはウィジェットに関連付けられた状態を取得するために使用することができます。


# 感想

日が空いてしまったので大分忘れてしまっている部分があり、読み進めるのに苦労しました…。継続が一番大事ですね😭
ウィジェットについては開発する上で必ず理解していないといけない部分だと思うので、何度も読み直してきちんと理解していきたいと思います！

次回は[Building layouts](https://flutter.dev/docs/development/ui/layout/tutorial)を読み進めてレイアウト作成について学ぼうと思います🌊🏄‍♀️