带有返回数据的 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: () =&gt; 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 按钮的主要内容,如果未能解决你的问题,请参考以下文章

带有软键盘和“返回”按钮的 EditText

Flutter中如何使用WillPopScope

Flutter中如何使用WillPopScope

Flutter:带有数据的 iOS 向后滑动手势

Flutter 如何处理 Stream 和 Future 返回错误

Flutter:使用不同的数据和返回按钮刷新同一屏幕