【问题标题】:Flutter: Call a function on a child widget's stateFlutter:在子小部件的状态上调用函数
【发布时间】:2018-11-06 13:03:22
【问题描述】:

我创建了一个有状态的小部件,它的父小部件需要调用一个处于子状态的函数。

具体来说,我有一个 PlayerContainer 类,它创建一个 VideoPlayer,并有一个 VideoPlayerController 的成员变量。当我按下播放按钮时,我的主类需要调用 state 的 VideoPlayerController 上的 play(),所以我在 State 类中创建了一个函数,但我不知道如何从父窗口小部件访问该函数。

这可能吗?还是我做错了?

【问题讨论】:

    标签: flutter


    【解决方案1】:

    我知道我参加聚会已经很晚了,但我有一些我认为可能会有所帮助的东西。因此,您需要在 VideoPlayerController 班级中做四 (4) 件事:
    1. 创建您的状态类的实例。
    2. 创建一个可以在您的PlayerContainer 类中访问的方法(播放)
    3. 在您的方法中,使用VideoPlayerControllerState 实例调用您的状态类中的方法。
    4. 最后,当您createState 时,请使用您已经创建的实例。

    class VideoPlayerController extends StatefulWidget {
      final VideoPlayerControllerState vpcs = VideoPlayerControllerState();
    
      void play() {
        vpcs.play();
      }
    
      @override
      State<StatefulWidget> createState() => vpcs;
    }
    

    如您所见,play 方法使用vpcs(VideoPlayerControllerState 实例)调用已在您的状态类中的播放方法。

    在你的 PlayerContainer 类中,使用你的成员变量来调用 play 方法。

    class PlayerContainerState extends State<PlayerContainer> {
      VideoPlayerController _vpc;
    
      @override
      void initState() {
        super.initState();
        _vpc = VideoPlayerController();
      }
      ...
    
      void _handlePressPlay(){
        _vpc.play();
      } 
      ...
    
      @override
      Widget build(BuildContext context) {
        return ... //your video player widget using _vpc as your VideoPlayerController
          _vpc,
        );
      }
    }
    

    您可以通过播放按钮的onPressed 方法调用_handlePressPlay()。或者,只需将_vpc.play() 放入onPressed 方法中。你的选择:-)。

    【讨论】:

    • 'package:flutter/src/widgets/framework.dart':断言失败:第 3819 行第 12 行:'_state._widget == null':不正确。
    • 它确实有效。无论如何,它对我来说没有任何错误。谢谢。
    【解决方案2】:

    状态管理是他们在这个框架方面投入的一件事。我使用了静态变量(如果我需要在状态之间共享数据)和 GlobalKeys(只是一个快速、肮脏的解决方案)来完成这项工作。我们应该使用 InheritedWidgets,这对于应该很简单的事情来说是非常不合时宜的。我通常只是这样做:

    // top of code here - this is global
    final videoPlayerKey = GlobalKey();
    
    class VideoPlayerContainer extends StatelessWidget {
        static VideoPlayerController videoPlayerController;
        ...
        @override
        Widget build(BuildContext context) {
            videoPlayerController = VideoPlayerController(...);
            // the static variable is empty until the container is built
    
            return Container(
                child: VideoPlayer(
                    child: PlayButton(onTap: () => 
                        videoPlayerKey.currentState.setState(
                        () => VideoPlayerContainer.videoPlayerController.play();
                    ))
                ),
            ); 
        }
    }
    
    class VideoPlayer extends StatefulWidget {
    final Key key = videoPlayerKey;
        ...
    }
    
    class VideoPlayerState extends State<VideoPlayer> {
        ...
    }
    

    我们需要获取 videoPlayerKey 的 currentState 以使用 setState() 并重新运行 build 方法以便它知道更新,然后我们可以使用静态变量。它可能在 VideoPlayer 中或在 VideoPlayerContainer 中不存在的任何其他地方,因为它是静态的 - 将 GlobalKey 分配给需要重建的任何 Widget 非常重要。它将起作用,因为每当用户可以点击按钮时,静态变量将被设置为任何 void 以由 VideoPlayerContainer 的 build() 方法读取。对于此方法,重要的是要注意将 GlobalKey 附加到需要更新的元素上更重要 - 您可以将静态 pageController 放在任何位置,并从 build() 或 initState() 中的任何位置进行设置。

    注意:如果您尝试在一个布局中使用多个 VideoPlayer,这将不起作用,因为 GlobalKeys 必须是唯一的,并且所有 VideoPlayers 都将使用相同的键进行初始化。这比任何事情都更像是一个肮脏的黑客。我目前正在开发更强大的状态管理解决方案来解决此类问题。

    【讨论】:

    • 我同意。作用域模型似乎很容易解决,或者只是使用静态。
    【解决方案3】:

    虽然 Darren Cole 的 answer above 无法正常工作,但有一种简单的方法可以规避眼前的问题。而不是

    State<StatefulWidget> createState() => vpcs;
    
    final VideoPlayerControllerState vpcs = VideoPlayerControllerState();
    

    写:

    State<StatefulWidget> createState(){
      vpcs = VideoPlayerControllerState();
      return vpcs;
    }
    
    VideoPlayerControllerState vpcs;
    
    

    这样,每次调用createState() 时都会重写状态,从而避免Failed assertion: line 3819 pos 12: '_state._widget == null': is not true 错误。

    不过,我想这是一个有点骇人听闻的解决方案 - 有人能指出一个更好的解决方案吗?

    【讨论】:

    • 附带说明:这将生成一个'must_be_immutable' warning,但我相信其后果比最终的State 实例要温和得多。
    【解决方案4】:

    使用简单的应用程序,您可以简单地在与 VideoPlayer 相同的 Widget 中创建播放按钮。通过将 PlayerContainer 与其父级组合,您可以增加 Widget State 范围的大小,以便需要访问它的所有内容都是单个更大 Widget 的一部分。

    子 Widget 受祖先影响的主要方式是:通过使用不同的参数重新构建,或者通过监听祖先更改的内容。对于后者,您可以在孩子的某处使用 InheritedWidget。如果子项引用 InheritedWidget,它会在 IW 更改时重建。另一种方法是监听祖先生成的事件流。

    您可能会发现在一次构建中构建整个页面是最简单的,直到它变得笨拙为止。

    【讨论】:

    • 谢谢,理查德!我需要了解 InheritedWidgets...我最终通过将 VideoPlayerController 移出状态到小部件本身并在小部件上添加 play() 函数来解决我的问题。
    猜你喜欢
    • 1970-01-01
    • 2021-10-17
    • 2019-04-26
    • 2021-12-04
    • 1970-01-01
    • 2020-01-05
    • 1970-01-01
    • 2019-06-26
    • 1970-01-01
    相关资源
    最近更新 更多