【问题标题】:call method in one stateful widget from another stateful widget - Flutter从另一个有状态小部件调用一个有状态小部件中的方法 - 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??  
      }
    );
  }
}

【问题讨论】:

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

标签: flutter dart


【解决方案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 中调用一个方法,正确的方法是什么? stackoverflow.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

    【讨论】:

      猜你喜欢
      • 2021-01-22
      • 2020-07-31
      • 2021-07-12
      • 1970-01-01
      • 1970-01-01
      • 2021-12-24
      • 1970-01-01
      • 2020-01-03
      • 1970-01-01
      相关资源
      最近更新 更多