dart - 保留或恢复页面状态的抽屉导航
问题描述
我正在构建一个 Flutter 应用程序,我正试图围绕导航和状态。我在下面构建了一个非常简单的应用程序,它有两个页面,上面都有增量按钮。它们都共享一个Scaffold
定义,以便在两个页面上都有一个一致的导航抽屉。
基本上我想要的功能就是FirstPage
单SecondPage
例。因此,如果您将计数器递增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))
])));
}
}
解决方案
我建议不要扩展 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 架构模型,包括使用继承的小部件。
推荐阅读
- python - Python中csv阅读器的时间复杂度是多少?
- c# - 如何处理 C# 上的 stathreadattribute 错误?
- terraform - 提供者 provider.terraform 不支持资源类型“terraform_remote_state
- curl - weaviate error code 400 parsing body from ...失败的无效字符'G'寻找对象的开头
- php - 通过 Ajax 过滤产品并在 laravel 中分页
- flutter - Flutter 在应用程序购买中我应该在 _verifyPurchase() 中包含什么
- angular - 完整的日历事件在 Angular 9 中不起作用
- scala - 在 Set.fold 中使用 null 作为值会生成 NoSuchElement 异常
- regex - 替换熊猫数据框系列中的每个值
- php - 将 Laravel 用户列表中的密码转换为在非 Laravel PHP 程序中使用