web-dev-qa-db-ja.com

Flutterでツリーから一時的に削除されたときにウィジェットの状態を保持する

ウィジェットの状態を保存しようとしています。そのため、ステートフルウィジェットをウィジェットツリーから一時的に削除し、後で再度追加すると、ウィジェットは、削除する前と同じ状態になります。これが私が持っている簡単な例です:

import 'package:flutter/material.Dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  bool showCounterWidget = true;
  @override
  Widget build(BuildContext context) {

    return Material(
      child: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            showCounterWidget ? CounterButton(): Text("Other widget"),
            SizedBox(height: 16,),
            FlatButton(
              child: Text("Toggle Widget"),
              onPressed: (){
                setState(() {
                showCounterWidget = !showCounterWidget;
              });
                },
            )
          ],
        ),
      ),
    );
  }
}

class CounterButton extends StatefulWidget {
  @override
  _CounterButtonState createState() => _CounterButtonState();
}

class _CounterButtonState extends State<CounterButton> {
  int counter = 0;

  @override
  Widget build(BuildContext context) {
    return MaterialButton(
      color: Colors.orangeAccent,
      child: Text(counter.toString()),
      onPressed: () {
        setState(() {
          counter++;
        });
      },
    );
  }
}

Here what it looks like when you run this code

理想的には、状態をリセットしたくないので、カウンターは0にリセットされません。カウンターウィジェットの状態をどのように保存しますか?

3

ウィジェットが一時的にツリーから削除されたときにウィジェットがその状態を失うのは、ジョシュアが述べたように、ウィジェットが要素/状態を失うためです。

今あなたは尋ねるかもしれません:

Element/Stateをキャッシュして、次にウィジェットが挿入されたときに、新しく作成する代わりに以前のウィジェットを再利用できるようにできませんか?

これは有効なアイデアですが、違います。できません。 Flutterはそれをアンチパターンとして判断し、その状況では例外をスローします。

代わりに、ウィジェットツリー内のウィジェットを無効な状態に保つ必要があります。

このようなことを達成するには、次のようなウィジェットを使用できます。

  • IndexedStack
  • 可視性/オフステージ

これらのウィジェットを使用すると、ウィジェットツリー内にウィジェットを保持できます(その状態を維持するため)が、そのレンダリング/アニメーション/セマンティクスは無効になります。

そのため、代わりに:

Widget build(context) {
  if (condition)
    return Foo();
  else
    return Bar();
}

それらを切り替えるとFoo/Barの状態が失われます

行う:

IndexedStack(
  index: condition ? 0 : 1, // switch between Foo and Bar based on condition
  children: [
    Foo(),
    Bar(),
  ],
)

このコードを使用すると、Foo/Barnotの間でやり取りするときに状態を失います。

2
Rémi Rousselet

ウィジェットは、スコープとライフタイム内に独自の一時データを格納することを目的としています。

あなたが提供したものに基づいて、ウィジェットを削除してウィジェットに追加し直すことにより、CounterButton子ウィジェットを再作成しようとしています木。

この場合、CounterButtonの下のカウンター値は保存されませんでしたまたはは保存されていませんMyHomePage画面の親ウィジェット、ビューモデルまたは参照への参照なし最上位レベル内または最上位の状態管理。

Flutterがウィジェットをレンダリングする方法のより技術的な概要

ウィジェットのコンストラクタを作成しようとした場合、keyとは何でしょうか?

class CounterButton extends StatefulWidget {
  const CounterButton({Key key}) : super(key: key);

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

キー(key)は、ウィジェットツリー内のウィジェットのインスタンスを区別するためにFlutterフレームワークによって自動的に処理および使用される識別子です。 削除および追加ウィジェット(CounterButton)ウィジェットツリーはそれに割り当てられたkeyをリセットします。したがって、ウィジェットツリーが保持するデータとその状態も削除されます。

注:パラメータとしてWidgetのみが含まれる場合、a keyのコンストラクタを作成する必要はありません。

ドキュメントから:

通常、別のウィジェットの唯一の子であるウィジェットは、明示的なキーを必要としません。

FlutterがCounterButtonに割り当てられたキーを変更するのはなぜですか?

Flutterが2つのオブジェクトを完全に区別する理由は、CounterButtonであるStatefulWidgetTextであるStatelessWidgetを切り替えることです。

Dart Devtoolsを使用して、変更を検査し、Flutterアプリの動作を切り替えることができます。

#3a4d2の最後にある_CounterButtonStateに注目してください。 image-1

これは、ウィジェットを切り替えた後のウィジェットツリー構造です。 CounterButtonからTextウィジェットへ。 image-2

2つのウィジェットが完全に異なるため、CounterButton#31a53で終わることが以前の識別子と異なることがわかります。 。 image-2

何ができますか?

ランタイム中に変更されたデータを_MyHomePageStateに保存し、コールバック関数を使用してCounterButtonにコンストラクターを作成し、呼び出しウィジェットの値を更新することをお勧めします。

counter_button.Dart

class CounterButton extends StatefulWidget {
  final counterValue;
  final VoidCallback onCountButtonPressed;

  const CounterButton({Key key, this.counterValue, this.onCountButtonPressed})
      : super(key: key);

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

class _CounterButtonState extends State<CounterButton> {
  @override
  Widget build(BuildContext context) {
    return MaterialButton(
      color: Colors.orangeAccent,
      child: Text(widget.counterValue.toString()),
      onPressed: () => widget.onCountButtonPressed(),
    );
  }
}

_counterValueで変数に_MyHomePageStateという名前を付けたとすると、次のように使用できます。

home_page.Dart

_showCounterWidget
    ? CounterButton(
        counterValue: _counterValue,
        onCountButtonPressed: () {
          setState(() {
            _counterValue++;
          });
        })
    : Text("Other widget"),

さらに、このソリューションは、CounterButtonまたは他の同様のウィジェットをアプリの他の部分で再利用するのに役立ちます。

完全な例を dartpad.dev に追加しました。

AndrewとMattは、Flutterが内部でウィジェットをレンダリングする方法について素晴らしい講演をしました。

さらに読む

1