在Flutter中访问小部件状态实例

时间:2020-09-28 13:02:24

标签: flutter dart

我无法从其他类访问Flutter中的对象(或状态)实例。我已经尝试过在网络上使用类似问题进行许多摆弄,并且目前正在使用“ GlobalKey”,但是我无法使其正常工作。

我正在尝试制作一个简单的Flutter应用程序,在此应用程序上,只需按一下按钮即可从另一个类访问Widget的状态:

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:null无效成员:'next'。

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

2 个答案:

答案 0 :(得分: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,
          )
        ],
      ),
    );
  }
}

答案 1 :(得分:1)

Flutter是一个声明性框架。在这种环境中,每当您要更改视图(或界面)时,都需要重建它。而且,如果您重建保持状态的内容,则会使其失去状态。这就是为什么它不应该负责保持程序状态的原因。

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

在您的特定情况下,由于它比较简单,因此可以使用全局对象或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

根据MobXInheritedWidget

widget的基类,这些widget有效地将信息向下传播 树。

您可以将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。

(...)
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。