如何从 Flutter 中的其他 StatefulWidget 设置/更新 StatefulWidget 的状态?

Posted

技术标签:

【中文标题】如何从 Flutter 中的其他 StatefulWidget 设置/更新 StatefulWidget 的状态?【英文标题】:How to Set/Update State of StatefulWidget from other StatefulWidget in Flutter? 【发布时间】:2018-07-06 23:41:57 【问题描述】:
    例如在下面的代码中加号按钮可以工作并且能够更新 文本,但减号按钮没有。 但是如果我们按下 FloatingActionButton 则状态会被刷新。 减号按钮正在更改变量的值,但没有 更新父窗口小部件的状态。

这里是代码.....

import 'package:flutter/material.dart';

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

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


int number;

EdgeInsets globalMargin = const EdgeInsets.symmetric(horizontal: 20.0, vertical: 20.0);
TextStyle textStyle = const TextStyle(
  fontSize: 100.0,
  color: Colors.black,
);

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

  final String title;

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


class _MyHomePageState extends State<MyHomePage> 
  @override
  void initState() 
    super.initState();
    number = number ?? 0;
  

  @override
  Widget build(BuildContext context) 
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Column(
        children: <Widget>[
          new Text(
            number.toString(),
            style: textStyle,
          ),
          new GridView.count(
            crossAxisCount: 2,
            shrinkWrap: true,
            scrollDirection: Axis.vertical,
            children: <Widget>[
              new InkResponse(
                child: new Container(
                    margin: globalMargin,
                    color: Colors.green,
                    child: new Center(
                      child: new Text(
                        "+",
                        style: textStyle,
                      ),
                    )),
                onTap: () 
                  setState(() 
                    number = number + 1;
                  );
                ,
              ),
              new Sub(),
            ],
          ),
        ],
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: () 
          setState(() );
        ,
        child: new Icon(Icons.update),
      ),
    );
  


class Sub extends StatefulWidget 
  @override
  _SubState createState() => new _SubState();


class _SubState extends State<Sub> 
  @override
  Widget build(BuildContext context) 
    return new InkResponse(
      child: new Container(
          margin: globalMargin,
          color: Colors.red,
          child: new Center(
            child: new Text(
              "-",
              style: textStyle,
            ),
          )),
      onTap: () 
        setState(() 
          number = number - 1;
        );
      ,
    );
  

【问题讨论】:

我真的很喜欢 scoped_model 来控制应用范围内的数据。 看看这个帖子***.com/questions/49491860/… 你可以使用继承的小部件 【参考方案1】:

屏幕截图(父对子,子对父):

这个例子展示了调用方法

    在父小部件的子小部件中定义。 在父小部件中从子小部件中定义。

代码:

class ParentPage extends StatelessWidget 
  final GlobalKey<ChildPageState> _key = GlobalKey();

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(title: Text("Parent")),
      body: Center(
        child: Column(
          children: <Widget>[
            Expanded(
              child: Container(
                color: Colors.grey,
                width: double.infinity,
                alignment: Alignment.center,
                child: ElevatedButton(
                  child: Text("Call method in child"),
                  onPressed: () => _key.currentState!.methodInChild(), // calls method in child
                ),
              ),
            ),
            Text("Above = Parent\nBelow = Child"),
            Expanded(
              child: ChildPage(
                key: _key,
                function: methodInParent,
              ),
            ),
          ],
        ),
      ),
    );
  

  methodInParent() => Fluttertoast.showToast(msg: "Method called in parent", gravity: ToastGravity.CENTER);


class ChildPage extends StatefulWidget 
  final VoidCallback function;

  ChildPage(Key? key, required this.function) : super(key: key);

  @override
  ChildPageState createState() => ChildPageState();


class ChildPageState extends State<ChildPage> 
  @override
  Widget build(BuildContext context) 
    return Container(
      color: Colors.teal,
      width: double.infinity,
      alignment: Alignment.center,
      child: ElevatedButton(
        child: Text("Call method in parent"),
        onPressed: () => widget.function(), // calls method in parent
      ),
    );
  

  methodInChild() => Fluttertoast.showToast(msg: "Method called in child");

【讨论】:

如果我的“_ChildPageState”在其他班级怎么调用? 从_ChildPageState中去掉_,下划线_表示该类是私有的。 我喜欢这种方法,还有一件事要补充的是照顾 _key.currentState 并检查它是否不为空。【参考方案2】:
class HomePage extends StatefulWidget 

  @override
  HomePageState createState() => HomePageState();


class HomePageState extends State<HomePage> 

  int selectedIndex = 0;

   void setSelectedIndex(int index)
     setState(() 
      selectedIndex = index;
     );
  


class TestPage extends StatefulWidget 

  @override
  TestPageState createState() => TestPageState();


class TestPageState extends State<TestPage> 
  int selectedIndex = 0;

  @override
  Widget build(BuildContext context) 
     return  GestureDetector(
                      onTap: ()

                        final HomePageState state = context.findAncestorStateOfType<HomePageState>();

                        state.setSelectedIndex(4);

                      ,
                    child: Container(
                        width: 100,
                        height: 100,
                        color: Colors.green
              )
     );
  


【讨论】:

请为您的回答提供一些背景信息。它可以帮助问题的其他访问者轻松理解您的方法。【参考方案3】:

旧解决方案

    创建 _MyHomePageState 的全局实例。在 _SubState 中将此实例用作 _myHomePageState.setState 无需创建全局实例。相反,只需将父实例传递给子小部件

新解决方案:传递回调似乎比上述解决方案更好

根据 FLUTTER 2.0 更新的代码

import 'package:flutter/material.dart';

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

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


EdgeInsets globalMargin =
    const EdgeInsets.symmetric(horizontal: 20.0, vertical: 20.0);
TextStyle textStyle = const TextStyle(
  fontSize: 100.0,
  color: Colors.black,
);

class MyHomePage extends StatefulWidget 
  @override
  _MyHomePageState createState() => _MyHomePageState();


class _MyHomePageState extends State<MyHomePage> 
  int number = 0;

  @override
  Widget build(BuildContext context) 
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('SO Help'),
      ),
      body: new Column(
        children: <Widget>[
          new Text(
            number.toString(),
            style: textStyle,
          ),
          new GridView.count(
            crossAxisCount: 2,
            shrinkWrap: true,
            scrollDirection: Axis.vertical,
            children: <Widget>[
              new InkResponse(
                child: new Container(
                    margin: globalMargin,
                    color: Colors.green,
                    child: new Center(
                      child: new Text(
                        "+",
                        style: textStyle,
                      ),
                    )),
                onTap: () 
                  setState(() 
                    this.number++;
                  );
                ,
              ),
              new Sub(onTap: () 
                setState(() 
                  this.number--;
                );
              ),
            ],
          ),
        ],
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: () 
          setState(() );
        ,
        child: new Icon(Icons.update),
      ),
    );
  


class Sub extends StatelessWidget 
  final Function onTap;
  Sub(this.onTap);

  @override
  Widget build(BuildContext context) 
    return new InkResponse(
      child: new Container(
        margin: globalMargin,
        color: Colors.red,
        child: new Center(
          child: new Text(
            "-",
            style: textStyle,
          ),
        ),
      ),
      onTap: this.onTap,
    );
  

只要让我知道它是否有效。

【讨论】:

工作但很糟糕。至少,使用context.ancestorStateOfType之类的东西 @RémiRousselet 你能举个例子来说明如何使用它吗? @RémiRousselet originalStateOfType 方法在 v1.12.1 之后被弃用。使用findAncestorStateOfType 代替api.flutter.dev/flutter/widgets/BuildContext/… @RémiRousselet 此链接明确表示最好使用回调。 @RémiRousselet 我同意使用回调是更好的解决方案。相应地更新了答案。 ??【参考方案4】:

这是对我有用的解决方案。

输出: 添加物品后,购物车小部件的状态会更新。

通过调用trigger from anywhere为要更新的小部件创建globalKey

final GlobalKey<CartWidgetState> cartKey = GlobalKey();

确保它保存在具有全局访问权限的文件中,以便可以从任何地方访问它。 我将它保存在 globalClass 中,其中通过应用程序的状态保存常用变量。

class CartWidget extends StatefulWidget 

  CartWidget(Key key) : super(key: key);
  @override
  CartWidgetState createState() => CartWidgetState();


class CartWidgetState extends State<CartWidget> 
  @override
  Widget build(BuildContext context) 
    //return your widget
    return Container();
  

从其他类调用您的小部件。

class HomeScreen extends StatefulWidget 

  HomeScreen (Key key) : super(key: key);
  @override
  HomeScreenState createState() => HomeScreen State();


class HomeScreen State extends State<HomeScreen> 
  @override
  Widget build(BuildContext context) 
    return ListView(
              children:[
                 ChildScreen(), 
                 CartWidget(key:cartKey)
              ]
            );
  




class ChildScreen extends StatefulWidget 

  ChildScreen (Key key) : super(key: key);
  @override
  ChildScreenState createState() => ChildScreen State();


class ChildScreen State extends State<ChildScreen> 
  @override
  Widget build(BuildContext context) 
    return InkWell(
              onTap: ()
                // This will update the state of your inherited widget/ class
                if (cartKey.currentState != null)
                    cartKey.currentState.setState(() );
              ,
              child: Text("Update The State of external Widget"),
           );
  

【讨论】:

将定义的全局定义键传递给 StatefulWidget 的变量键对我有用。【参考方案5】:

旧的,但我会根据我的发现添加我的答案:

var ancestralState = context.findAncestorStateOfType<ParentState>();
      ancestralState.setState(() 
        // here you can access public vars and update state.
        ...
      );

【讨论】:

【参考方案6】:

我想扩展 Mohamed Elrashid 的答案,以防您需要将变量从子小部件传递到父小部件

在子小部件上:

class ChildWidget extends StatefulWidget 
  final Function() notifyParent;
  ChildWidget(Key key, @required this.notifyParent) : super(key: key);

在父小部件上

void refresh(dynamic childValue) 
  setState(() 
    _parentVariable = childValue;
  );

在父小部件上:将上面的函数传递给子小部件

new ChildWidget( notifyParent: refresh ); 

在子小部件上:使用子小部件中的任何变量调用父函数

widget.notifyParent(childVariable);

【讨论】:

我试过你的方法,但是我在widget.notifyParent(childVariable); 行收到警告,说:位置参数太多:预期为 0,但找到了 1。 它应该可以工作。可能你在第二部分有问题void refresh()。我仍然不是这种方法的忠实拥护者,代码容易出错。祝你好运! @rb2030 现在检查一下,你应该在函数中添加动态变量 @evals,请再次检查,因为您的编辑不正确。正如我在上面的其他评论中所说,将数据传递给父小部件不是使用 Flutter 的方法,应该避免。祝你好运。 @AlvinKonda 良好的传递数据与我一起工作,你的答案从未奏效,无论如何谢谢:)【参考方案7】:

尽管这些先前的答案中的大多数都可以使用,但我建议您探索提供程序或 BloC 架构,这两种架构都是 Google 推荐的。

简而言之,后者将创建一个流,当状态发生变化时,它会向窗口小部件树中的窗口小部件报告,它会更新所有相关视图,而不管它是从哪里更新的。

这是一个很好的概述,您可以阅读以了解有关该主题的更多信息:https://bloclibrary.dev/#/

【讨论】:

或者,您可以使用事件驱动架构。最简单的。为什么我们不能使用 eventify?看看这里***.com/questions/46057353/… 如果有这样的例子就好了,所有这些解决方案似乎都过于复杂了。【参考方案8】:

1.On Child Widget : 添加参数 Function 参数

class ChildWidget extends StatefulWidget 
  final Function() notifyParent;
  ChildWidget(Key key, @required this.notifyParent) : super(key: key);

2.On Parent Widget : 创建一个Function让孩子回调

refresh() 
  setState(() );

3.On Parent Widget : 将 parentFunction 传递给 Child Widget

new ChildWidget( notifyParent: refresh );  

4.On Child Widget:调用父函数

  widget.notifyParent();

【讨论】:

我该如何做相反的事情。我想从父母更新孩子 如果孩子是一个有状态的小部件,我想从父母那里更新它怎么办? @Farhana refresh(argument1,argument1) etState(() ); |小部件.notifyParent(1,2); 这是“应该的”解决方案吗?这是flutter团队使用的吗?我在那个代码中闻到了麻烦。或者也许只是我的鼻子太敏感了:D 对于那些想要将pass 值传递给子小部件的人,您可以在父小部件中创建ValueNotifier,并将其传递给子小部件。在您的子小部件中,利用 ValueListenableBuilder 构建布局。

以上是关于如何从 Flutter 中的其他 StatefulWidget 设置/更新 StatefulWidget 的状态?的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 中 stateless 和 stateful widget 的区别[Flutter专题60]

Flutter Stateful Widget 状态未初始化

Flutter: Stateful 挂件 vs Stateless 挂件

Flutter - Stateful(有状态) 和 stateless(无状态) widgets

跨多个屏幕使用的 Flutter Stateful Widget 正在重建

Flutter Stateful Widget 重新创建 State