首页 > 解决方案 > 保留或恢复页面状态的抽屉导航

问题描述

我正在构建一个 Flutter 应用程序,我正试图围绕导航和状态。我在下面构建了一个非常简单的应用程序,它有两个页面,上面都有增量按钮。它们都共享一个Scaffold定义,以便在两个页面上都有一个一致的导航抽屉。

基本上我想要的功能就是FirstPageSecondPage例。因此,如果您将计数器递增FirstPage几次,转到SecondPage然后FirstPage通过抽屉返回(而不是后退按钮),FirstPage其计数器仍应递增。

现在,如果你这样做,它似乎会创建一个新实例,FirstPage因为Navigation.push(). 此外,如果您FirstPage出于某种原因使用抽屉再次单击“首页”,则不应丢失状态。

我在这里看到一个 Flutter 开发人员提到了“非线性导航”这个词,这让我觉得这样的事情是可能的。谢谢你的帮助。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(title: "Navigation", home: FirstPage());
  }
}

class MyScaffold extends Scaffold {
  final BuildContext context;
  final Text title;
  final Widget body;

  MyScaffold({
    @required this.context,
    @required this.title,
    @required this.body,
  })  : assert(context != null),
        assert(title != null),
        assert(body != null),
        super(
            appBar: AppBar(title: title),
            drawer: Drawer(
                child: ListView(padding: EdgeInsets.zero, children: <Widget>[
              SizedBox(height: 100.0),
              ListTile(
                  title: Text("First Page"),
                  onTap: () => Navigator.of(context).push(MaterialPageRoute(
                      builder: (BuildContext context) => FirstPage()))),
              ListTile(
                  title: Text("Second Page"),
                  onTap: () => Navigator.of(context).push(MaterialPageRoute(
                      builder: (BuildContext context) => SecondPage()))),
            ])),
            body: body);
}

class FirstPage extends StatefulWidget {
  @override
  _FirstPageState createState() => _FirstPageState();
}

class _FirstPageState extends State<FirstPage> {
  int _counter = 0;
  void _increment() => setState(() => _counter++);

  @override
  Widget build(BuildContext context) {
    return MyScaffold(
        context: context,
        title: Text("First Page"),
        body: Center(
            child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
              Text('$_counter'),
              RaisedButton(onPressed: _increment, child: Icon(Icons.add))
            ])));
  }
}

class SecondPage extends StatefulWidget {
  @override
  _SecondPageState createState() => _SecondPageState();
}

class _SecondPageState extends State<SecondPage> {
  int _counter = 0;
  void _increment() => setState(() => _counter++);

  @override
  Widget build(BuildContext context) {
    return MyScaffold(
        context: context,
        title: Text("Second Page"),
        body: Center(
            child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
              Text('$_counter'),
              RaisedButton(onPressed: _increment, child: Icon(Icons.add))
            ])));
  }
}

标签: dartflutter

解决方案


我建议不要扩展 Scaffold,而是创建一个 StatelessWidget 来构建一个脚手架并将子小部件作为参数。通过按照您的方式进行操作,您不会得到 acontext或任何东西,并且通常建议封装而不是 Flutter 中的继承。

除此之外,您还可以分离前进/后退的逻辑。您可以执行一次push(secondPage)然后pop返回第一页,而不是每次都推送一个新页面。flutter中的导航是一个栈;每次推送时,您都会添加到它的顶部,每次弹出时,您都会删除顶部元素。

如果你想让 SecondPage 保留它的计数器,那就有点困难了。您在这里有几个选项 - 第一个是在构建它时传入一个初始值。然后它会在内部增加该值,然后在您调用时Navigator.pop(context, [value])将该值传回。它将被保存到第一个屏幕的状态,然后当您再次推送页面时,您将传递新的初始值。

第二个选项是将计数器的值保留在高于第二页的级别(使用 StatefulWidget 和可能的 InheritedWidget) - 即在哪个小部件中保存您的导航器/材料应用程序。不过,这可能有点矫枉过正。

编辑:针对 OP 的评论,已经明确表示实际上有几页和更复杂的信息,而不仅仅是一个计数器。

有多种方法可以解决这个问题;一种是使用Redux作为策略来实现应用程序;当与flutter_persist一起使用时,它可以成为一个非常强大的持久状态工具,我相信还有一个插件可以与firestore集成以进行云备份(不确定)。

不过,我自己并不是 redux 的忠实粉丝。它增加了相当多的开销,在我看来这与 Flutter 的简单性背道而驰(尽管我可以看到它如何为大型应用程序或团队创建一致性)。

更简单的选项如编辑前所述;使用 InheritedWidget 包含更高级别的状态。我在下面做了一个简单的例子:

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => new MyAppState();
}

class MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return CounterInfo(
      child: new MaterialApp(
        routes: {
          "/": (context) => new FirstPage(),
          "/second": (context) => new SecondPage(),
        },
      ),
    );
  }
}

class _InheritedCounterInfo extends InheritedWidget {
  final CounterInfoState data;

  _InheritedCounterInfo({
    Key key,
    @required this.data,
    @required Widget child,
  }) : super(key: key, child: child);

  @override
  bool updateShouldNotify(_InheritedCounterInfo old) => true;
}

class CounterInfo extends StatefulWidget {
  final Widget child;

  const CounterInfo({Key key, this.child}) : super(key: key);

  static CounterInfoState of(BuildContext context) {
    return (context.inheritFromWidgetOfExactType(_InheritedCounterInfo) as _InheritedCounterInfo).data;
  }

  @override
  State<StatefulWidget> createState() => new CounterInfoState();
}

class CounterInfoState extends State<CounterInfo> {

  int firstCounter = 0;
  int secondCounter = 0;

  void incrementFirst() {
    setState(() {
      firstCounter++;
    });
  }

  void incrementSecond() {
    setState(() {
      secondCounter++;
    });
  }

  @override
  Widget build(BuildContext context) => new _InheritedCounterInfo(data: this, child: widget.child);
}

class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counterInfo = CounterInfo.of(context);

    return new MyScaffold(
      title: "First page",
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text("${counterInfo.firstCounter}"),
            RaisedButton(onPressed: counterInfo.incrementFirst, child: Icon(Icons.add)),
          ],
        ),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counterInfo = CounterInfo.of(context);

    return new MyScaffold(
      title: "Second page",
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text("${counterInfo.secondCounter}"),
            RaisedButton(onPressed: counterInfo.incrementSecond, child: Icon(Icons.add)),
          ],
        ),
      ),
    );
  }
}

class MyScaffold extends StatelessWidget {
  final String title;
  final Widget child;

  const MyScaffold({Key key, this.title, this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: AppBar(title: new Text(title)),
      drawer: Drawer(
          child: ListView(padding: EdgeInsets.zero, children: <Widget>[
        SizedBox(height: 100.0),
        ListTile(
          title: Text("First Page"),
          onTap: () => Navigator.of(context).pushReplacementNamed("/"),
        ),
        ListTile(title: Text("Second Page"), onTap: () => Navigator.of(context).pushReplacementNamed("/second")),
      ])),
      body: child,
    );
  }
}

无论您是采用这种方式还是考虑使用 Redux,都是一个有用的网站,其中包含各种 Flutter 架构模型,包括使用继承的小部件。


推荐阅读