在 Flutter 中临时从树中移除时保留小部件状态

Art*_*ine 7 state state-management dart flutter statefulwidget

我正在尝试保留小部件的状态,以便如果我暂时从小部件树中删除有状态小部件,然后稍后重新添加它,小部件将具有与删除之前相同的状态。这是我的一个简化示例:

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++;
        });
      },
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

理想情况下,我不希望状态重置,因此计数器不会重置为 0,我将如何保留计数器小部件的状态?

Rém*_*let 14

正如 Joshua 所说,小部件暂时从树中移除时失去其状态的原因是因为它失去了它的元素/状态。

现在你可能会问:

我不能缓存元素/状态以便下次插入小部件时,它可以重用前一个而不是重新创建它们吗?

这是一个有效的想法,但不是。你不能。Flutter 将其判断为反模式,并会在这种情况下抛出异常。

您应该做的是将小部件保持在小部件树中,处于禁用状态。

为了实现这样的事情,你可以使用小部件,如:

  • 索引堆栈
  • 能见度/后台

这些小部件将允许您将小部件保留在小部件树中(以保持其状态),但禁用其渲染/动画/语义。

因此,而不是:

Widget build(context) {
  if (condition)
    return Foo();
  else
    return Bar();
}
Run Code Online (Sandbox Code Playgroud)

在它们之间切换时,这会使Foo/Bar失去它们的状态

做:

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

使用此代码,则Foo/Bar不会做了一回,并将它们之间来回时失去它们的状态。

  • 我认为自己对 Flutter 有一定的了解,并且我不知道小部件在从小部件树中删除时会失去状态,即使该小部件之前已实例化。也许应该在“第一步”指南中注明这一点。 (2认同)

Jos*_*man 5

小部件旨在在其范围和生命周期内存储自己的瞬态数据。

根据您提供的内容,您正在尝试通过将其删除并添加回小部件树来重新创建CounterButton 子部件。

在这种情况下, 下的计数器值CounterButton 未保存或未保存MyHomePage屏幕、父窗口小部件中,没有任何对视图模型或顶层内或顶层的任何状态管理的引用。

更技术性的概述 Flutter 如何渲染你的 widget

key有没有想过如果您尝试为小部件创建构造函数会发生什么?

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

  @override
  _CounterButtonState createState() => _CounterButtonState();
}
Run Code Online (Sandbox Code Playgroud)

key ( key) 是由 Flutter 框架自动处理和使用的标识符,用于区分 widget 树中的 widget 实例。在控件树中删除添加控件 ( ) 会重置分配给它的控件,因此它所保存的数据及其状态也会被删除。CounterButtonkey

Widget注意:如果 a 仅包含作为其参数,则无需为 a 创建构造函数key

从文档中:

通常,作为另一个小部件的唯一子部件的小部件不需要显式键。

为什么 Flutter 会更改分配给 CounterButton 的按键?

你在CounterButtonwhich is aStatefulWidgetTextwhich is a之间切换StatelessWidget,这就是Flutter 识别出两个完全不同的对象的原因。

您始终可以使用 Dart Devtools 来检查更改并切换 Flutter 应用程序的行为。

请密切关注#3a4d2末尾处_CounterButtonState图片-1

这是切换小部件后的小部件树结构。从CounterButtonText小部件。 图片-2

您现在可以看到以 ,CounterButton结尾的标识符#31a53与之前的标识符不同,因为这两个小部件完全不同图片-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(),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

_counterValue假设您在 中命名了变量_MyHomePageState,您可以像这样使用它:

主页.dart

_showCounterWidget
    ? CounterButton(
        counterValue: _counterValue,
        onCountButtonPressed: () {
          setState(() {
            _counterValue++;
          });
        })
    : Text("Other widget"),
Run Code Online (Sandbox Code Playgroud)

CounterButton此外,此解决方案将帮助您在应用程序的其他部分重用或其他类似的小部件。

我已在dartpad.dev中添加了完整的示例。

Andrew 和 Matt 做了精彩的演讲,介绍了 Flutter 如何在底层渲染小部件:

进一步阅读

  • 嗨,雷米,感谢您分享您的想法。我认为回答“为什么”与OP的“如何”是齐头并进的。根据OP的问题,他显然“试图”保留小部件状态。我只是假设他可能不熟悉小部件渲染的工作原理,如果我尝试更深入地了解为什么会发生这种行为,这可能也会有所帮助。感谢您的宝贵时间,雷米。谢谢。 (4认同)