【问题标题】:Accessing Widget State instances in Flutter在 Flutter 中访问 Widget State 实例
【发布时间】:2021-01-14 01:39:21
【问题描述】:

我在从其他类访问 Flutter 中的对象(或状态)实例时遇到问题。我在网上尝试了很多类似的问题,目前正在使用“GlobalKey”,但我无法让它工作。

我正在尝试制作一个简单的 Flutter 应用程序,在按下按钮时,可以从另一个类访问小部件的状态:

import 'viewer.dart' as viewer;
(...)
onPressed: () {
    //Works
    print("Doing something");
    
    //Doesn't work
    viewer.key.currentState.nextPage();
},

我的 viewer.dart 文件看起来包含一个 PageController,以及一个包含该控制器的类:

final key = new GlobalKey<_RegistryState>();

final PageController _controller = PageController(
  initialPage: 0,
);


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

  @override
  _RegistryState createState() => _RegistryState();
}

class _RegistryState extends State<Registry> {
  void next() {
    print("Doing something!");
    _controller.nextPage();
  }

  @override
  Widget build(BuildContext context) {
    return PageView(
      //physics: NeverScrollableScrollPhysics(), //Disable user manually scrolling
      controller: _controller,
      children: [
        registry_screens.ScreenSplash(),
        registry_screens.ScreenName(),
        Text("Bye"),
      ],
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

这个想法是,只要按下按钮,PageController 就会移动到下一页(已经存在,我可以通过在屏幕上滑动手动滚动到它)。

应用程序编译正常,但按下按钮时出现错误“NoSuchMethodError: invalid member on null: 'next'”。

我是否使用正确的方法来访问小部件(或状态)的实例?

【问题讨论】:

  • 你的问题让我相信你对状态有核心误解。一个小部件不应该“直接访问”另一个小部件的状态。相反,我们把它留给一个小部件来为它下面的人提供函数,告诉他们如何修改父级的状态。基本上我建议非常仔细地阅读this
  • @DrSatan1 看来你是对的,我会好好看看基础知识

标签: flutter dart


【解决方案1】:

从外部访问状态通常是个坏主意。相反,外部类应该只通过它们公开的方法与 Widget 交互。

我刚刚制作了一个视频,介绍了与您使用 PageView 完全相同的入职设置,您可以在此处看到 - 我一步一步地进行操作:https://www.youtube.com/watch?v=ji__FEKSnMw

本质上是这样的:

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MainPage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

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

  @override
  _MainPageState createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  PageController pageController = new PageController(initialPage: 0);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: SafeArea(
        child: Container(
            child: PageView(
                controller: pageController,
                physics: NeverScrollableScrollPhysics(),
                children: [
              Slide(
                  hero: Image.asset("./assets/hero-1.png"),
                  title: "Boost your traffic",
                  subtitle:
                      "Outreach to many social networks to improve your statistics",
                  onNext: nextPage),
              Slide(
                  hero: Image.asset("./assets/hero-2.png"),
                  title: "Give the best solution",
                  subtitle:
                      "We will give best solution for your business isues",
                  onNext: nextPage),
              Slide(
                  hero: Image.asset("./assets/hero-3.png"),
                  title: "Reach the target",
                  subtitle:
                      "With our help, it will be easier to achieve your goals",
                  onNext: nextPage),
              Scaffold(
                backgroundColor: Colors.white,
                body: Center(
                  child: Text(
                    'Be kind to yourself',
                    style: kTitleStyle,
                  ),
                ),
              )
            ])),
      ),
    );
  }

  void nextPage() {
    pageController.nextPage(
        duration: const Duration(milliseconds: 200), curve: Curves.ease);
  }
}

class Slide extends StatelessWidget {
  final Widget hero;
  final String title;
  final String subtitle;
  final VoidCallback onNext;

  const Slide({Key key, this.hero, this.title, this.subtitle, this.onNext})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Expanded(child: hero),
          Padding(
            padding: const EdgeInsets.all(20),
            child: Column(
              children: [
                Text(
                  title,
                  style: kTitleStyle,
                ),
                SizedBox(
                  height: 20,
                ),
                Text(
                  subtitle,
                  style: kSubtitleStyle,
                  textAlign: TextAlign.center,
                ),
                SizedBox(
                  height: 35,
                ),
              ],
            ),
          ),
          GestureDetector(
            onTap: onNext,
            child: Text(
              "Skip",
              style: kSubtitleStyle,
            ),
          ),
          SizedBox(
            height: 4,
          )
        ],
      ),
    );
  }
}

【讨论】:

    【解决方案2】:

    Flutter 是一个声明式框架。在这种环境下,每次你想要改变视图(或界面)时,你都需要重新构建它。如果你重建持有国家的东西,你就会失去它。这就是为什么它不应该负责保持程序的状态。

    Flutter 中的状态管理是一个广泛的主题,有很多选择。正如 @DrSatan1 在 cmets 中提到的那样,在 Flutter.dev 中,您可以使用 Provider 找到有关状态管理的良好文档,但您有很多选择 BLoCReduXMobX 等。

    在您的特定场景中,由于它更简单,您可以使用全局对象或Inherited Widget 来完成。

    全局对象

    globals.dart

    currentPage=0;
    

    在小部件中

    import 'globals.dart' as global;
    (...)
    onPressed: () {
        setState((){
            globals.currentPage++;
        });
    },
    

    viewer.dart

      @override
      Widget build(BuildContext context) {
        return PageView(
          //physics: NeverScrollableScrollPhysics(), //Disable user manually scrolling
          currentPage: globals.currentPage, //instead of using PageController
          children: [
            registry_screens.ScreenSplash(),
            registry_screens.ScreenName(),
            Text("Bye"),
          ],
        );
      }
    

    您可以使用PageController 作为您的全局对象。在这种情况下,您可以将 PageController 向下传递到小部件树。在这种情况下,最好改用InheritedWidget

    InheritedWidget

    根据docsInheritedWidget

    小部件的基类,可有效地向下传播信息 树。

    您可以将 PageController 传递给树下的所有小部件。您的 viewer.dart 将是:

    (...)
     @override
      Widget build(BuildContext context) {
        return MyInheritedWidget (
          pageController: _controller,
          child: PageView(
            //physics: NeverScrollableScrollPhysics(), //Disable user manually scrolling
            //controller: _controller, // Don't pass controller here
            children: [
              registry_screens.ScreenSplash(),
              registry_screens.ScreenName(),
              Text("Bye"),
            ],
          );
        );
        
    }
    
    (...)
    // create the inherited widget wrapper. It could be done with [Builder][7] too, instead of a different Widget.
    
    class MyInheritedWidget extends InheritedWidget {
      final PageController pageController;
    
      MyInheritedWidget({
        Key key,
        @required Widget child,
        @required this.pageController,
      }) : super(key: key, child: child);
    
      @override
      bool updateShouldNotify(InheritedWidget oldWidget) => true;
    }
    (...)
    

    之后就可以访问PageView中的pageController或者它下面的任何Widget了。

    (...)
    onPressed: () {
        //Works
        print("Doing something");
        
        // Find closest InheritedWidget
        MyInheritedWidget myInheritedWidget = 
            context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>()
        // Get pageController from it
        PageController controller = myInheritedWidget.pageController
        // call nextPage()
        nextPage();
    },
    (...)
    

    虽然这两种方法都适用于您的特定场景,但您应该查看 Flutter Docs 有关状态管理的信息。也许你根本不需要 PageController。

    【讨论】:

      猜你喜欢
      • 2019-07-26
      • 2022-01-21
      • 1970-01-01
      • 1970-01-01
      • 2021-01-27
      • 1970-01-01
      • 2022-08-19
      • 2021-03-23
      • 1970-01-01
      相关资源
      最近更新 更多