带有返回数据的 Flutter Back 按钮
Posted
技术标签:
【中文标题】带有返回数据的 Flutter Back 按钮【英文标题】:Flutter Back button with return data 【发布时间】:2019-01-26 09:57:01 【问题描述】:我有一个带有两个按钮的界面,它们会弹出并返回 true 或 false,如下所示:
onPressed: () => Navigator.pop(context, false)
我需要调整应用栏中的后退按钮,所以它会弹出并返回 false。有没有办法做到这一点?
【问题讨论】:
【参考方案1】:试试这个:
void _onBackPressed()
// Called when the user either presses the back arrow in the AppBar or
// the dedicated back button.
@override
Widget build(BuildContext context)
return WillPopScope(
onWillPop: ()
_onBackPressed();
return Future.value(false);
,
child: Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: _onBackPressed,
),
),
),
);
【讨论】:
【参考方案2】:从屏幕返回数据
您可以使用Navigator.pop()
方法执行此操作,步骤如下:
-
定义主屏幕
主屏幕显示一个按钮。点击时,它会启动选择屏幕。
class HomeScreen extends StatelessWidget
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(
title: Text('Returning Data Demo'),
),
// Create the SelectionButton widget in the next step.
body: Center(child: SelectionButton()),
);
-
添加启动选择屏幕的按钮
现在,创建 SelectionButton,它执行以下操作:
-
点击选择屏幕时启动它。
等待 SelectionScreen 返回结果。
class SelectionButton extends StatelessWidget
@override
Widget build(BuildContext context)
return RaisedButton(
onPressed: ()
_navigateAndDisplaySelection(context);
,
child: Text('Pick an option, any option!'),
);
// A method that launches the SelectionScreen and awaits the
// result from Navigator.pop.
_navigateAndDisplaySelection(BuildContext context) async
// Navigator.push returns a Future that completes after calling
// Navigator.pop on the Selection Screen.
final result = await Navigator.push(
context,
// Create the SelectionScreen in the next step.
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
-
用两个按钮显示选择屏幕
现在,构建一个包含两个按钮的选择屏幕。当用户点击按钮时,该应用会关闭选择屏幕并让主屏幕知道点击了哪个按钮。
此步骤定义 UI。下一步添加代码以返回数据。
class SelectionScreen extends StatelessWidget
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(
title: Text('Pick an option'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: ()
// Pop here with "Yep"...
,
child: Text('Yep!'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: ()
// Pop here with "Nope"
,
child: Text('Nope.'),
),
)
],
),
),
);
When a button is tapped, close the selection screen
现在,更新两个按钮的 onPressed()
回调。要将数据返回到第一个屏幕,请使用 Navigator.pop()
方法,该方法接受名为 result
的可选第二个参数。任何结果都会在 SelectionButton 中返回到 Future。
RaisedButton(
onPressed: ()
// The Yep button returns "Yep!" as the result.
Navigator.pop(context, 'Yep!');
,
child: Text('Yep!'),
);
-
在主屏幕上显示带有选择的小吃栏
现在您正在启动一个选择屏幕并等待结果,您需要对返回的信息执行一些操作。
在这种情况下,使用SelectionButton
中的_navigateAndDisplaySelection()
方法显示一个显示结果的snackbar:
_navigateAndDisplaySelection(BuildContext context) async
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
// After the Selection Screen returns a result, hide any previous snackbars
// and show the new result.
Scaffold.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("$result")));
互动示例
class SelectionButton extends StatelessWidget
@override
Widget build(BuildContext context)
return RaisedButton(
onPressed: ()
_navigateAndDisplaySelection(context);
,
child: Text('Pick an option, any option!'),
);
// A method that launches the SelectionScreen and awaits the result from
// Navigator.pop.
_navigateAndDisplaySelection(BuildContext context) async
// Navigator.push returns a Future that completes after calling
// Navigator.pop on the Selection Screen.
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
// After the Selection Screen returns a result, hide any previous snackbars
// and show the new result.
Scaffold.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("$result")));
class SelectionScreen extends StatelessWidget
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(
title: Text('Pick an option'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: ()
// Close the screen and return "Yep!" as the result.
Navigator.pop(context, 'Yep!');
,
child: Text('Yep!'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: ()
// Close the screen and return "Nope!" as the result.
Navigator.pop(context, 'Nope.');
,
child: Text('Nope.'),
),
)
],
),
),
);
文档:Return data from a screen
【讨论】:
【参考方案3】:更简单的方法是将主体包裹在 WillPopScope 中,这样它就可以与 顶部的后退按钮和 Android 的后退按钮一起使用底部。
这里是两个后退按钮都返回 false 的示例:
final return = Navigator.of(context).push(MaterialPageRoute<bool>(
builder: (BuildContext context)
return Scaffold(
appBar: AppBar(
title: Text("New Page"),
),
body: WillPopScope(
onWillPop: () async
Navigator.pop(context, false);
return false;
,
child: newPageStuff(),
),
);
,
));
在他们建议使用的其他答案中:
前导:BackButton(...)
我发现这适用于顶部的后退按钮,而不适用于 android。
我还是举了一个例子:
final return = Navigator.of(context).push(MaterialPageRoute<bool>(
builder: (BuildContext context)
return Scaffold(
appBar: AppBar(
leading: BackButton(
onPressed: () => Navigator.pop(context, false),
),
title: Text("New Page"),
),
body: newPageStuff(),
);
,
));
【讨论】:
感谢您的回答,对我很有帮助!但我建议对您的第一个解决方案进行一些改进:您作为onWillPop
参数传递的函数可以是async
函数,因此在块的末尾您可以只是return false
。
在 onWillPop
的文档中,它说“如果回调返回解析为 false 的 Future,则不会弹出封闭路由”。那么异步回调本身是否应该返回 true?
@chanban async
函数正在处理 true
返回。 onWillPop
将在运行时返回 false
。
对我来说,我从第二个屏幕发回一个布尔值 true,但我在第一个屏幕上得到空值。对此有什么想法吗?【参考方案4】:
实现这一点的最简单方法是:
在您的身体中,将WillPopScope
作为父小部件
并在其onWillPop : ()
电话中
Navigator.pop(context, false);
onWillPop
of WillPopScope
将在您按下 AppBar 上的返回按钮时自动触发
【讨论】:
*onWillPop: ()【参考方案5】:这可能对您有所帮助和工作
第一屏
void goToSecondScreen()async
var result = await Navigator.push(_context, new MaterialPageRoute(
builder: (BuildContext context) => new SecondScreen(context),
fullscreenDialog: true,)
);
Scaffold.of(_context).showSnackBar(SnackBar(content: Text("$result"),duration: Duration(seconds: 3),));
第二屏
Navigator.pop(context, "Hello world");
【讨论】:
我不认为这个答案涵盖了处理后退按钮的细节。 我已经尝试过了。但是,在没有导航器的情况下使用 onwillpop 时不起作用【参考方案6】:要弹出数据并将数据传回导航,您需要使用屏幕 1 中的.then()
。下面是示例。
屏幕 2:
class DetailsClassWhichYouWantToPop
final String date;
final String amount;
DetailsClassWhichYouWantToPop(this.date, this.amount);
void getDataAndPop()
DetailsClassWhichYouWantToPop detailsClass = new DetailsClassWhichYouWantToPop(dateController.text, amountController.text);
Navigator.pop(context, detailsClass); //pop happens here
new RaisedButton(
child: new Text("Edit"),
color: UIData.col_button_orange,
textColor: Colors.white,
onPressed: getDataAndPop, //calling pop here
),
屏幕 1:
class Screen1 extends StatefulWidget
//var objectFromEditBill;
DetailsClassWhichYouWantToPop detailsClass;
MyBills(Key key, this.detailsClass) : super(key: key);
@override
Screen1State createState() => new Screen1State();
class Screen1State extends State<Screen1> with TickerProviderStateMixin
void getDataFromEdit(DetailsClassWhichYouWantToPop detailClass)
print("natureOfExpense Value:::::: " + detailClass.date);
print("receiptNumber value::::::: " + detailClass.amount);
void getDataFromEdit(DetailsClassWhichYouWantToPop detailClass)
print("natureOfExpense Value:::::: " + detailClass.natureOfExpense);
print("receiptNumber value::::::: " + detailClass.receiptNumber);
void pushFilePath(File file) async
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Screen2(fileObj: file),
),
).then((val)
getDataFromScreen2(val); //you get details from screen2 here
);
【讨论】:
【参考方案7】:您可以将数据/参数从一个屏幕传递到另一个屏幕,
考虑这个例子:
screen1.dart:
import 'package:flutter/material.dart';
import 'screen2.dart';
class Screen1 extends StatelessWidget
Screen1(this.indx);
final int indx;
@override
Widget build(BuildContext context)
return new S1(indx: indx,);
class S1 extends StatefulWidget
S1(Key key, this.indx) : super(key: key);
final int indx;
@override
S1State createState() => new S1State(indx);
class S1State extends State<VD>
int indx = 5;
@override
Widget build(BuildContext context)
return new Scaffold(
appBar: new AppBar(
leading: new IconButton(icon: const Icon(Icons.iconName), onPressed: ()
Navigator.pushReplacement(context, new MaterialPageRoute(
builder: (BuildContext context) => new Screen2(indx),
));
),
),
);
屏幕 2:
import 'package:flutter/material.dart';
import 'screen2.dart';
class Screen2 extends StatelessWidget
Screen2(this.indx);
final int indx;
@override
Widget build(BuildContext context)
return new S2(indx: indx,);
class S2 extends StatefulWidget
S2(Key key, this.indx) : super(key: key);
final int indx;
@override
S2State createState() => new S2State(indx);
class S2State extends State<VD>
int indx = 1;
@override
Widget build(BuildContext context)
return new Scaffold(
appBar: new AppBar(
leading: new IconButton(icon: const Icon(Icons.Icons.arrow_back), onPressed: ()
Navigator.pushReplacement(context, new MaterialPageRoute(
builder: (BuildContext context) => new Screen1(indx),
));
),
),
);
要在屏幕之间传递数据,请将参数/数据传递给 Navigator.pushReplacement()
中的 Screens 构造函数。您可以传递任意数量的参数。
这一行
Navigator.pushReplacement(context, new MaterialPageRoute(
builder: (BuildContext context) => new Screen1(indx),
));
将转到 Screen1 并调用 Screen1 的 initState 和 build 方法,以便您可以获取更新的值。
【讨论】:
【参考方案8】:虽然您可以覆盖自定义行为的后退按钮,但不要这样做。
您应该处理 null 场景,而不是使用自定义弹出覆盖按钮。 您不想手动覆盖图标的原因有几个:
ios 和 Android 上的图标更改。在 IOS 上它使用arrow_back_ios
而 android 使用 arrow_back
如果没有返回路线,图标可能会自动消失
物理后退按钮仍将返回null
。
应该做以下事情:
var result = await Navigator.pushNamed<bool>(context, "/");
if (result == null)
result = false;
【讨论】:
由于用户可能按下了硬件返回按钮,如何在不调用Navigator.pop
的情况下从页面返回数据?【参考方案9】:
默认的BackButton
接管AppBar
的前导属性,因此您只需使用自定义后退按钮覆盖leading
属性,例如:
leading: IconButton(icon:Icon(Icons.chevron_left),onPressed:() => Navigator.pop(context, false),),)
【讨论】:
好答案!!这对我帮助很大。但是,我们应该使用 BackButtonIcon() 代替 Chevron 以确保大小正确。请参阅文档:docs.flutter.io/flutter/material/BackButton-class.html 和 docs.flutter.io/flutter/material/BackButtonIcon-class.html Android 在操作系统中内置了一个后退按钮。 iOS允许通过向右滑动来导航。 Scaffold 的leading
属性中的自定义按钮不会处理这些其他导航方式。
这会反击AppBar的点击,但如果用户按下Android手机底部的系统后退按钮,它不会反击。
短:leading: BackButton(onPressed: () => Navigator.pop(context, false),),
【参考方案10】:
使用以下代码从您的活动中获取结果。
Future _startActivity() async
Map results = await Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context)
return new StartActivityForResult();
));
if (results != null && results.containsKey('item'))
setState(()
stringFromActivity = results['item'];
print(stringFromActivity);
);
完整的源代码
import 'package:flutter/material.dart';
import 'activity_for_result.dart';
import 'dart:async';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget
@override
Widget build(BuildContext context)
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Start Activity For Result'),
);
class MyHomePage extends StatefulWidget
MyHomePage(Key key, this.title) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
class _MyHomePageState extends State<MyHomePage>
String stringFromActivity = 'Start Activity To Change Me \n???';
@override
Widget build(BuildContext context)
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
stringFromActivity, style: new TextStyle(fontSize: 20.0), textAlign: TextAlign.center,
),
new Container(height: 20.0,),
new RaisedButton(child: new Text('Start Activity'),
onPressed: () => _startActivity(),)
],
),
),
);
Future _startActivity() async
Map results = await Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context)
return new StartActivityForResult();
));
if (results != null && results.containsKey('item'))
setState(()
stringFromActivity = results['item'];
print(stringFromActivity);
);
import 'package:flutter/material.dart';
class StartActivityForResult extends StatelessWidget
List<String>list = ['???','???','???','???','???','???','?','?','?',];
@override
Widget build(BuildContext context)
// TODO: implement build
return new Scaffold(
appBar: new AppBar(
title: new Text('Selecte Smily'),
),
body: new ListView.builder(itemBuilder: (context, i)
return new ListTile(title: new Text(list[i]),
onTap: ()
Navigator.of(context).pop('item': list[i]);
,
);
, itemCount: list.length,),
);
获取有关如何工作的完整运行示例 here
【讨论】:
【参考方案11】:首先,移除自动附加的后退按钮(参见this answer)
然后,创建您自己的后退按钮。像这样:
IconButton(
onPressed: () => Navigator.pop(context, false),
icon: Icon(Icons.arrow_back),
)
【讨论】:
以上是关于带有返回数据的 Flutter Back 按钮的主要内容,如果未能解决你的问题,请参考以下文章