从另一个有状态小部件调用一个有状态小部件中的方法 - Flutter

Posted

技术标签:

【中文标题】从另一个有状态小部件调用一个有状态小部件中的方法 - Flutter【英文标题】:call method in one stateful widget from another stateful widget - Flutter 【发布时间】:2018-12-04 09:27:28 【问题描述】:

我有一个正在处理的颤振项目,我无法将整个代码放入其中,因为它的代码超过 500 行,所以我将尝试像我使用 imp 一样简单地问我的问题。部分代码。

我有一个有状态的小部件,并且在扩展State<MusicPlayer>的类下的有状态小部件中有一些函数

文件lib\main.dart

只需要一个简单的函数,比如

class MyAppState extends State<MyApp>
...
void printSample ()
  print("Sample text");

...

这个函数在主类的有状态小部件中。

还有一个文件lib\MyApplication.dart

这个文件也有一个有状态的小部件,我可以做点什么,这样我就可以在这里调用函数printSample() ..

class MyApplicationState extends State<MyApplication>
...
@override
  Widget build(BuildContext context) 
    return new FlatButton(
      child: new Text("Print Sample Text"),
      onPressed :()
       // i want to cal the function here how is it possible to call the 
       // function 
       // printSample()  from here??  
      
    );
  

【问题讨论】:

Trigger a function from a widget to a State object的可能重复 如果你能确保一个小部件是另一个小部件的后代,你可以使用InheritedWidget @JacobPhillips 在那种情况下使用context.ancestorStateOfType @RémiRousselet 我需要更多帮助,我正在尝试将数据从子小部件发送到父小部件,所有流和可听的示例都相反,这正是我的问题所在.我想从一个子小部件调用一个函数到一个父小部件,很抱歉没有提到上面的内容.. 如果您的孩子也应该提交活动,请将整个 StreamController 传递给您的孩子,而不仅仅是 Stream 【参考方案1】:

要调用父级的函数,可以使用回调模式。在此示例中,将一个函数 (onColorSelected) 传递给子进程。当按下按钮时,孩子调用该函数:

import 'package:flutter/material.dart';

class Parent extends StatefulWidget 
  @override
  State<StatefulWidget> createState() 
    return ParentState();
  


class ParentState extends State<Parent> 
  Color selectedColor = Colors.grey;

  @override
  Widget build(BuildContext context) 
    return Column(
      children: <Widget>[
        Container(
          color: selectedColor,
          height: 200.0,
        ),
        ColorPicker(
          onColorSelect: (Color color) 
            setState(() 
              selectedColor = color;
            );
          ,
        )
      ],
    );
  


class ColorPicker extends StatelessWidget 
  const ColorPicker(this.onColorSelect);

  final ColorCallback onColorSelect;

  @override
  Widget build(BuildContext context) 
    return Row(
      children: <Widget>[
        RaisedButton(
          child: Text('red'),
          color: Colors.red,
          onPressed: () 
            onColorSelect(Colors.red);
          ,
        ),
        RaisedButton(
          child: Text('green'),
          color: Colors.green,
          onPressed: () 
            onColorSelect(Colors.green);
          ,
        ),
        RaisedButton(
          child: Text('blue'),
          color: Colors.blue,
          onPressed: () 
            onColorSelect(Colors.blue);
          ,
        )
      ],
    );
  


typedef ColorCallback = void Function(Color color);

内部 Flutter 小部件(如按钮或表单字段)使用完全相同的模式。如果您只想调用不带任何参数的函数,则可以使用VoidCallback 类型,而不是定义自己的回调类型。


如果您想通知更高的父级,您可以在每个层次结构级别上重复此模式:

class ColorPickerWrapper extends StatelessWidget 
  const ColorPickerWrapper(this.onColorSelect);

  final ColorCallback onColorSelect;

  @override
  Widget build(BuildContext context) 
    return Padding(
      padding: EdgeInsets.all(20.0),
      child: ColorPicker(onColorSelect: onColorSelect),
    )
  


在 Flutter 中不鼓励从父窗口小部件调用子窗口小部件的方法。相反,Flutter 鼓励您将孩子的状态作为构造函数参数传递下去。您只需在父窗口小部件中调用 setState 即可更新其子窗口,而不是调用子窗口的方法。


另一种方法是 Flutter 中的 controller 类(ScrollControllerAnimationController、...)。这些也作为构造函数参数传递给子级,它们包含控制子级状态的方法,而无需在父级上调用setState。示例:

scrollController.animateTo(200.0, duration: Duration(seconds: 1), curve: Curves.easeInOut);

然后要求子级监听这些更改以更新其内部状态。当然,你也可以实现自己的控制器类。如果需要,我建议您查看 Flutter 的源代码以了解其工作原理。


Future 和 Streams 是传递状态的另一种选择,也可以用于调用子函数。

但我真的不推荐它。如果你需要调用一个子部件的方法,那很像你的应用架构有缺陷。 尝试将状态提升到共同祖先!

【讨论】:

“不鼓励从父小部件调用子小部件的方法”即使通过 GlobalKey 调用也不鼓励?? 有点复杂!! 好的,谢谢!这是一个救生员。我有一个从浮动操作按钮调用的大对话框,其中定义了许多本地下拉列表,并且不想复制/粘贴它,但也希望在子状态页面中使用相同的浮动操作按钮。这正是我想要的。 @boformer 如果我想在不同的类中定义这些类,在一个 dart 文件中定义 ColorPicker,在不同的类中定义(Parent,ParentState),那么我应该在哪里定义 typedef ColorCallback,因为我尝试过但它失败了 太棒了,我从这个答案中学到了很多东西,它帮助我避免了构建 Flutter 应用程序的一些陷阱。谢谢boformer!【参考方案2】:

我通过反复试验找到了另一个解决方案,但它奏效了。

import 'main.dart' as main;

然后在onPressed下面添加这一行。

main.MyAppState().printSample();

【讨论】:

这是反模式,你应该提升状态 这里你正在实例化一个新的状态对象,这是完全错误的。 @ProCo 我的更正是删除这个答案。 你能给它的替代方案吗,这个答案很简单,但我不知道它是如何工作的。【参考方案3】:

如果你想调用 printSample() 函数,你可以使用:

class Myapp extends StatefulWidget
...
    final MyAppState myAppState=new MyAppState();
    @override
    MyappState createState() => MyAppState();
    void printSample()
        myAppState.printSample();
    

class MyAppState extends State<MyApp>
    void printSample ()
        print("Sample text");
    


...............
Myapp _myapp = new Myapp();
_myapp.printSample();
...

【讨论】:

我认为这不适用于更复杂的情况:Myapp StatefulWidget 可以重复构造,因此任何给定实例持有的状态可能与 Flutter 持有和关联的状态对象不同和树一起,对吧? 你好@PatNiemeyer,对吗? _StatefulButtonState _lastState; //覆盖状态 createState() _lastState = new _StatefulButtonState(onPressed: onPressed);返回_lastState; 【参考方案4】:

你可以试一试,它会从Page1 (StatefulWidget) 小部件调用Page2 (StatefulWidget) 中定义的方法。

class Page1 extends StatefulWidget 
  @override
  _Page1State createState() => _Page1State();


class _Page1State extends State<Page1> 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: Center(
        child: RaisedButton(
          child: Text("Call page 2 method"),
          onPressed: () => Page2().method(),
        ),
      ),
    );
  


class Page2 extends StatefulWidget 
  method() => createState().methodInPage2();

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


class _Page2State extends State<Page2> 
  methodInPage2() => print("method in page 2");

  @override
  Widget build(BuildContext context) => Container();

【讨论】:

这个答案与 Mehmet Akif BAYSAL 的答案完全相同(概念 100% 相同)。 Mehmet Akif BAYSAL 的答案在代码示例中得到了更雄辩的说明,因此更容易理解。 如果我想在 bloc 中调用一个方法,正确的方法是什么? ***.com/questions/62097660/…【参考方案5】:

您可以通过使用小部件的 key 来做到这一点

myWidget.dart

    class MyWidget extends StatefulWidget 
     
      const MyWidget (Key key) : super(key: key);
     
      @override
      State<StatefulWidget> createState()=> MyState();
    
  
    

   class MyState extends State<MyWidget >
        
          
               
      Widget build(BuildContext context) return ....
    
      void printSample ()
         print("Sample text");
      
        
 

现在在使用 MyWidget 时将 GlobalKey 声明为全局键

  GlobalKey<MyState> _myKey = GlobalKey();

并在创建小部件时传递它

MyWidget(
key : _myKey,
)

通过这个键你可以调用状态内的任何公共方法

_myKey.currentState.printSample();

【讨论】:

你不是说_myKey.currentState.printSample();吗? 我卡住了,无法使用 GlobalKey,因为我的状态小部件前面有下划线,如 _MyState,更改为 MyState - 然后它工作正常。 @J.Diaz 下划线前的类名指私有类也用于变量和成员,所以你不能从目录外访问它【参考方案6】:

这里的 HomePage 是父页面, ChildPage 是子页面。有一个方法叫做 onSelectItem,我们需要从子页面调用它。

class HomePage extends StatefulWidget 

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


class HomePageState extends State<HomePage> 

  onSelectItem(String param) 
    print(param);
  

  @override Widget build(BuildContext context) 

  


class ChildPage extends StatefulWidget 
  final HomePageState homePageState;

  ChildPage(Key key, @required this.homePageState) : super(key: key);

  _ChildPageState createState() => _ChildPageState();


class _ChildPageState extends State<ChildPage> 
  @override Widget build(BuildContext context) 

    return RaisedButton(
      onPressed: () 
        widget.homePageState.onSelectItem("test");
      ,
      child: const Text(
          'Click here',
          style: TextStyle(fontSize: 20)
      ),
    );
  

所以,通过使用widget父类状态,我们可以调用父类方法。

【讨论】:

【参考方案7】:

虽然使用回调和GlobalKey's 对于简单的用例来说很好,但对于更复杂的设置,它们可能应该被视为反模式,因为它们挂钩到小部件类型并依赖于低级实现逻辑。

如果您发现自己添加了越来越多的回调/全局键,并且开始变得混乱,那么可能是时候切换到 StreamController + StreamSubscription 之类的东西了。通过这种方式,您可以将事件与特定的小部件类型分离,并抽象出小部件间的通信逻辑。

注册一个事件控制器

在您的***小部件(应用级别或页面级别,取决于您的需要)中创建StreamController 实例,并确保在dispose() 方法中释放它:

class _TopLevelPageState extends State<TopLevelPage> 
  StreamController<MyCustomEventType> eventController = StreamController<MyCustomEventType>.broadcast();

// ...
  @override
  void dispose() 
    eventController.close();
    super.dispose();
  

eventController 实例作为构造函数参数传递给任何需要监听事件和/或触发事件的子小部件。

MyCustomEventType 可以是枚举(如果您不需要传递额外数据)或常规对象,其中包含您需要的任何字段,以防您需要为事件设置额外数据。

触发事件

现在在任何小部件(包括您声明StreamController 的父小部件)中,您可以通过以下方式触发事件:

eventController.sink.add(MyCustomEventType.UserLoginIsComplete);

收听事件

要在您的孩子(或父小部件)中设置监听器,请将以下代码放入 initState()


class _ChildWidgetState extends State<ChildWidget> 
  @override
  void initState() 
    super.initState();
    // NOTE: 'widget' is the ootb reference to the `ChildWidget` instance. 
    this.eventSubscription = widget.eventController.stream.asBroadcastStream().listen((event) 
      if (event == MyCustomEventType.UserLoginIsComplete) 
        print('handling LOGIN COMPLETE event ' + event.toString());
       else 
        print('handling some other event ' + event.toString());
      
  

  @override
  void dispose() 
    this.parentSubscription.cancel();
    super.dispose();
  

请注意,如果您覆盖 StreamController.done(),那么您的侦听器将不会触发,因为 done() 会替换您之前设置的任何侦听器。


注意:如果您在两个小部件之间具有一对一的通信关系,那么您不需要广播事件风格 - 在这种情况下,您可以在没有 .broadcast() 的情况下创建控制器,即使用 StreamController&lt;MyCustomEventType&gt;()并且要收听而不是.stream.asBroadcastStream().listen(),您可以使用.stream.listen()。另见https://api.dart.dev/stable/dart-async/Stream-class.html

有关概述此方法和其他方法的答案,请参阅Inter Widget communication

【讨论】:

以上是关于从另一个有状态小部件调用一个有状态小部件中的方法 - Flutter的主要内容,如果未能解决你的问题,请参考以下文章

如何从另一个小部件更改有状态小部件的变量?

从另一个小部件更改一个小部件中的状态

如何从另一个小部件状态处理一个小部件状态?

如何从另一个小部件类更新小部件树 - Flutter

如何从另一个小部件设置状态?

Flutter:如何从浮动操作按钮调用小部件状态类的方法