草庐IT

dart - 保留或恢复页面状态的抽屉式导航

coder 2023-07-22 原文

我正在构建一个 Flutter 应用程序,我正试图将注意力集中在导航和状态上。我在下面构建了一个非常简单的应用程序,它有两个页面,上面都有递增按钮。它们都共享一个 Scaffold 定义,因此两个页面上都有一个一致的抽屉导航。

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

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

我在这里看到一位 Flutter 开发人员提到术语“non-linear navigation”,这让我觉得这样的事情是可能的。感谢您的帮助。

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))
            ])));
  }
}

最佳答案

与其扩展 Scaffold,我建议制作一个 StatelessWidget,它构建一个脚手架并将一个子 widget 作为参数。按照您的方式进行操作,您不会获得 context 或任何东西,并且在 Flutter 中通常建议封装而不是继承。

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

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

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

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

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

不过我自己并不是 redux 的 super 粉丝;它增加了相当多的开销,在我看来这与 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,this是一个有用的网站,具有各种 Flutter 架构模型,包括使用继承的小部件。

关于dart - 保留或恢复页面状态的抽屉式导航,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51067916/

有关dart - 保留或恢复页面状态的抽屉式导航的更多相关文章

  1. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  2. ruby-on-rails - 跳过状态机方法的所有验证 - 2

    当我的预订模型通过rake任务在状态机上转换时,我试图找出如何跳过对ActiveRecord对象的特定实例的验证。我想在reservation.close时跳过所有验证!叫做。希望调用reservation.close!(:validate=>false)之类的东西。仅供引用,我们正在使用https://github.com/pluginaweek/state_machine用于状态机。这是我的预订模型的示例。classReservation["requested","negotiating","approved"])}state_machine:initial=>'requested

  3. ruby - 字符串文字中的转义状态作为 `String#tr` 的参数 - 2

    对于作为String#tr参数的单引号字符串文字中反斜杠的转义状态,我觉得有些神秘。你能解释一下下面三个例子之间的对比吗?我特别不明白第二个。为了避免复杂化,我在这里使用了'd',在双引号中转义时不会改变含义("\d"="d")。'\\'.tr('\\','x')#=>"x"'\\'.tr('\\d','x')#=>"\\"'\\'.tr('\\\d','x')#=>"x" 最佳答案 在tr中转义tr的第一个参数非常类似于正则表达式中的括号字符分组。您可以在表达式的开头使用^来否定匹配(替换任何不匹配的内容)并使用例如a-f来匹配一

  4. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  5. ruby-on-rails - 为模型创建状态属性 - 2

    我想为我的Task模型创建一个status属性,该属性将按以下顺序指示它在三部分进度中的位置:打开=>进行中=>完成。它的工作方式类似于亚马逊包裹的交付方式:已订购=>已发货=>已交付。我想知道设置此属性的最佳方法是什么。我可能是错的,但创建三个独立的bool属性似乎有点多余。实现此目标的最佳方法是什么? 最佳答案 Rails4有一个内置的enummacro.它使用单个整数列并映射到键列表。classOrderenumstatus:[:ordered,:shipped,:delivered]end状态映射如下:{ordered:0,

  6. ruby - 是否可以在不实际发送或读取数据的情况下查明 ruby​​ 套接字是否处于 ESTABLISHED 或 CLOSE_WAIT 状态? - 2

    s=Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0)s.connect(Socket.pack_sockaddr_in('port','hostname'))ssl=OpenSSL::SSL::SSLSocket.new(s,sslcert)ssl.connect从这里开始,如果ssl连接和底层套接字仍然是ESTABLISHED,或者它是否在默认值7200之后进入CLOSE_WAIT,我想检查一个线程几秒钟甚至更糟的是在实际上不需要.write()或.read()的情况下关闭。是用select()、IO.select()还是其他方法完成

  7. ruby - 在 ASP 页面上 Mechanize 中断 - 2

    require'mechanize'agent=Mechanize.newlogin=agent.get('http://www.schoolnet.ch/DE/HomeDE.htm')agent.clicklogin.link_withtext:/Login/然后我得到Mechanize::UnsupportedSchemeError。 最佳答案 Mechanize不支持javascript但您可以将搜索字段添加到表单并为其分配搜索词并使用mechanize提交表单form=page.forms.firstform.add_fie

  8. ruby - 在 ruby​​ 中生成一个进程,捕获 stdout,stderr,获取退出状态 - 2

    我想从ruby​​rake脚本运行一个可执行文件,比如foo.exe我希望将foo.exe的STDOUT和STDERR输出直接写入我正在运行rake任务的控制台.当进程完成时,我想将退出代码捕获到一个变量中。我如何实现这一目标?我一直在玩backticks、process.spawn、system但我无法获得我想要的所有行为,只有部分更新:我在Windows上,在标准命令提示符下,而不是cygwin 最佳答案 system获取您想要的STDOUT行为。它还返回true作为零退出代码,这可能很有用。$?填充了有关最后一次system调

  9. ruby-on-rails - prawnto 显示新页面时不会中断的表格 - 2

    我有可变数量的表格和可变数量的行,我想让它们一个接一个地显示,但如果表格不适合当前页面,请将其放在下一页,然后继续。我已将表格放入事务中,以便我可以回滚然后打印它(如果高度适合当前页面),但我如何获得表格高度?我现在有这段代码pdf.transactiondopdf.table@data,:font_size=>12,:border_style=>:grid,:horizontal_padding=>10,:vertical_padding=>3,:border_width=>2,:position=>:left,:row_colors=>["FFFFFF","DDDDDD"]pdf.

  10. ruby - 每个页面上的 Jekyll 分页 - 2

    据我们所知,Jekyll默认分页仅支持index.html,我想创建blog.html并在那里包含分页。有什么解决办法吗? 最佳答案 如果您创建一个名为/blog的目录并在其中放置一个index.html文件,那么您可以向_config.yml表示paginate_path:"blog/page:num"。不是使用根文件夹中的默认index.html作为分页器模板,而是使用/blog/index.html。分页器将根据需要生成类似/blog/page2/和/blog/page3/的页面。这将使您到达yourwebsite.com/b

随机推荐