Flutter第十三弹 路由和导航

目标:

1.Flutter怎么创建路由?

2.怎么实现路由跳转?页面返回?

一、路由

1.1 什么是路由?

路由(Route)在移动开发中通常指页面(Page),在Android中通常指一个Activity。所谓路由管理,就是管理页面之间如何跳转,通常也可被称为导航管理。这和原生开发类似,无论是Android还是iOS,导航管理都会维护一个路由栈,路由入栈(push)操作对应打开一个新页面,路由出栈(pop)操作对应页面关闭操作,而路由管理主要是指如何来管理路由栈。

路由通常通过维护一个路由表,建立页面导航表。

1.2 路由导航

Dart 复制代码
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 需要返回MaterialApp,MaterialApp内部已经实现了Navigator
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("首页")),
        body: Column(
          children: [
            Text("这是第一页"),
            RaisedButton(
              onPressed: () {
                /// 实现点击事件
                /// TODO: 导航跳转第二页
                debugPrint("导航跳转第二页");
                // 定义导航路由(导航到SecondRoute)
                Navigator.push(context, MaterialPageRoute(builder: (_) {
                  return SecondRoute();
                }));
              },
              // 按钮显示内容
              child: Text("进入第二页"),
            )
          ],
        ),
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 返回页面为脚手架开始,公用MaterialApp
    return Scaffold(
      appBar: AppBar(
        title: Text("第二页"),
      ),
      body: Column(
        children: [
          Text("这是第二页"),
          RaisedButton(
            onPressed: () {
              /// 实现点击事件
              Navigator.pop(context);
            },
            // 按钮显示内容(返回上一页)
            child: Text("返回"),
          )
        ],
      ),
    );
  }
}

新建两个页面,第一个页面点击按钮,跳转第二个页面。

报错信息如下。

1.2.1 导航问题分析

导航操作请求使用了不包含Navigator的上下文context

`Navigator`实际上也是一个Widget,这个异常出现在`Navigator.of(context)`路由器的获取上,而这句代码会**从当前的context的父级一层层向上去查找一个`Navigator`**,我们当前传递的context就是MyApp,它的父级是root------UI根节点。`Navigator`这个widget的并不是由root创建的,因此在root下一级的上下文中无法获得`Navigator`。

在之前所有的路由案例中,我们的上下文是MainRoute,它的父级是MaterialApp。MaterialApp内部就会创建一个Navigator。

MaterialApp->\_MaterialAppState->WidgetsApp->\_WidgetsAppState

所以问题就在于,`Navigator`需要通过MaterialApp或者它孩子的上下文。

1.2.2 导航解决方案

Navigator必须在MaterialApp下一级,这样获取的Element的上下文才是MaterialApp的上下文。

解决方案一:MaterialApp下body提取一级MainRoute

新的层级结构

root

|---MaterialApp-->Navigator

|--------->MainRoute

是指MainRoute的层级在MaterialApp下一级。

这样,MainRoute就能够访问父Element的Navigator。

跳转第二页成功。

Dart 复制代码
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 需要返回MaterialApp,MaterialApp内部已经实现了Navigator
    return MaterialApp(
      home: MainRoute(),
    );
  }
}

class MainRoute extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("首页")),
      body: Column(
        children: [
          Text("这是第一页"),
          RaisedButton(
            onPressed: () {
              /// 实现点击事件
              /// TODO: 导航跳转第二页
              debugPrint("导航跳转第二页");
              // 定义导航路由(导航到SecondRoute)
              /// 因为context是MyApp的BuildContext,MyApp不包含Navigator,因此报错
              /// Navigator必须在MaterialApp下一级
              ///
              Navigator.push(context, MaterialPageRoute(builder: (context) {
                return SecondRoute();
              }));
              // Navigator.push(MaterialPageRoute(
              //
              // ))
            },
            // 按钮显示内容
            child: Text("进入第二页"),
          )
        ],
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 返回页面为脚手架开始,公用MaterialApp
    return Scaffold(
      appBar: AppBar(
        title: Text("第二页"),
      ),
      body: Column(
        children: [
          Text("这是第二页"),
          RaisedButton(
            onPressed: () {
              /// 实现点击事件
              Navigator.pop(context);
            },
            // 按钮显示内容(返回上一页)
            child: Text("返回"),
          )
        ],
      ),
    );
  }
}
解决方案二:MaterialApp.Builder构建子树

MaterialApp下的子控件Builder,通过Builder构建的子树,上下文是Builder,因此一定在MaterialApp下面。

1.3 命名路由

给页面增加路由名字,建立路由表。

1.3.1 注册路由表

MaterialApp.routes注册路由表。

路由表定义路由名称和对应的路由导航页面。

Dart 复制代码
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class RouteTable {
  static String ROUTE_MAIN = "/main";
  static String ROUTE_SECOND = "/second";
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 需要返回MaterialApp,MaterialApp内部已经实现了Navigator
    return MaterialApp(
      home: MainRoute(),
      routes: {
        RouteTable.ROUTE_MAIN: (_) {
          return new MainRoute();
        },
        RouteTable.ROUTE_SECOND: (_) {
          return new SecondRoute();
        }
      },
    );
  }
}

class MainRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("首页")),
      body: Column(
        children: [
          Text("这是第一页"),
          RaisedButton(
            onPressed: () {
              /// 实现点击事件
              /// TODO: 导航跳转第二页
              debugPrint("命名路由导航跳转第二页");
              // 定义导航路由(导航到SecondRoute)
              /// 因为context是MyApp的BuildContext,MyApp不包含Navigator,因此报错
              /// Navigator必须在MaterialApp下一级
              /// 命令路由跳转的时候采用路由表
              Navigator.pushNamed(context, RouteTable.ROUTE_SECOND);
              // Navigator.push(MaterialPageRoute(
              //
              // ))
            },
            // 按钮显示内容
            child: Text("进入第二页"),
          )
        ],
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 返回页面为脚手架开始,公用MaterialApp
    return Scaffold(
      appBar: AppBar(
        title: Text("第二页"),
      ),
      body: Column(
        children: [
          Text("这是第二页"),
          RaisedButton(
            onPressed: () {
              /// 实现点击事件
              Navigator.pop(context);
            },
            // 按钮显示内容(返回上一页)
            child: Text("返回"),
          )
        ],
      ),
    );
  }
}

1.3.2 路由导航

路由导航通过命名路由进行导航。

Dart 复制代码
Navigator.pushNamed(context, RouteTable.ROUTE_SECOND);

二、页面参数返回

在项目中,跳转一个新页面以后,处理完成,回到第一个页面,可能需要处理返回来的参数。

这就需要涉及到页面参数返回和接收。

2.1 返回参数保存

复制代码
Navigator.pop携带返回结果
Dart 复制代码
class Result {
  String name;
  int score;

  Result(this.name, this.score);

  @override
  String toString() {
    return 'Result{name: $name, score: $score}';
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 返回页面为脚手架开始,公用MaterialApp
    return Scaffold(
      appBar: AppBar(
        title: Text("第二页"),
      ),
      body: Column(
        children: [
          Text("这是第二页"),
          RaisedButton(
            onPressed: () {
              /// 实现点击事件
              /// 返回上一个页面,携带处理结果。例如当前处理结果是一个对象
              Navigator.pop(context, new Result("超新星", 100));
            },
            // 按钮显示内容(返回上一页)
            child: Text("返回"),
          )
        ],
      ),
    );
  }
}

Navigator.pop携带一个结果返回上一页。

2.2 接收返回结果

第一页需要接收页面返回结果

2.2.1 onPress方法修改为异步方法 async

对应异步接收处理的方法,声明为async。

Dart 复制代码
class MainRoute extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("首页")),
      body: Column(
        children: [
          Text("这是第一页"),
          RaisedButton(
            /// 1) 修改为异步任务,等待页面返回
            onPressed: () async {
              /// 实现点击事件
              debugPrint("导航跳转第二页");
              // 定义导航路由(导航到SecondRoute)
              /// 因为context是MyApp的BuildContext,MyApp不包含Navigator,因此报错
              /// Navigator必须在MaterialApp下一级
              /// 2) 通过await等待返回结果
              ///
              Result result =  await Navigator.push(context, MaterialPageRoute(builder: (context) {
                return SecondRoute();
              }));
              debugPrint("接收结果 result = " + result.toString());
            },
            // 按钮显示内容
            child: Text("进入第二页"),
          )
        ],
      ),
    );
  }
}

返回结果数据,是泛型数据,顶级类Object的子类。因此几乎所有类型都可以。

三、定制页面切换动画

Material库中提供了MaterialPageRoute,它在Android上会上下滑动切换。如果想自定义路由切换动画,可以使用PageRouteBuilder。

3.1 页面水平切换

导航到下一个页面的时候,增加水平滑动效果。

复制代码
SlideTransition是水平滑动动画,position定义平移的动画效果。

我们采用Tween补间动画效果。

Dart 复制代码
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class RouteTable {
  /// 首页默认使用 / 定义这个路由的话,MaterialApp的home不需要重复定义
  static String ROUTE_MAIN = "/";
  static String ROUTE_SECOND = "/second";
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 需要返回MaterialApp,MaterialApp内部已经实现了Navigator
    return MaterialApp(
      home: MainRoute(),
      // routes: {
      //   RouteTable.ROUTE_MAIN: (_) {
      //     return new MainRoute();
      //   },
      //   RouteTable.ROUTE_SECOND: (_) {
      //     return new SecondRoute();
      //   }
      // },
    );
  }
}

class MainRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("首页")),
      body: Column(
        children: [
          RaisedButton(
            onPressed: () {
              /// 实现点击事件
              debugPrint("命名路由导航跳转第二页");
              // 定义导航路由(导航到SecondRoute)
              /// 因为context是MyApp的BuildContext,MyApp不包含Navigator,因此报错
              /// Navigator必须在MaterialApp下一级
              /// push 的时候,增加路由跳转动画效果
              Navigator.push(context, PageRouteBuilder(pageBuilder:
                  (BuildContext context, Animation<double> animation,
                      Animation<double> secondaryAnimation) {
                return SlideTransition(
                  position: Tween<Offset>(
                    begin: const Offset(1.0, 0.0),
                    end: const Offset(0.0, 0.0),
                  ).animate(animation),
                  ///  child导航的第二个页面
                  child: SecondRoute(),
                );
              }));
            },
            // 按钮显示内容
            child: Text("进入第二页"),
          )
        ],
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 返回页面为脚手架开始,公用MaterialApp
    return Scaffold(
      appBar: AppBar(
        title: Text("第二页"),
      ),
      body: Column(
        children: [
          Text("这是第二页"),
          RaisedButton(
            onPressed: () {
              /// 实现点击事件
              Navigator.pop(context);
            },
            // 按钮显示内容(返回上一页)
            child: Text("返回"),
          )
        ],
      ),
    );
  }
}

需要注意切换到第二个页面,child为SecondRoute

3.2 渐变+滑动动画

在滑动动画外层嵌套一层渐变动画。

child对应滑动动画。

Dart 复制代码
class MainRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("首页")),
      body: Column(
        children: [
          RaisedButton(
            onPressed: () {
              /// 实现点击事件
              debugPrint("命名路由导航跳转第二页");
              // 定义导航路由(导航到SecondRoute)
              /// 因为context是MyApp的BuildContext,MyApp不包含Navigator,因此报错
              /// Navigator必须在MaterialApp下一级
              /// push 的时候,增加路由跳转动画效果
              Navigator.push(
                  context,
                  PageRouteBuilder(

                      /// 动画时长
                      transitionDuration: Duration(milliseconds: 500),
                      pageBuilder: (BuildContext context,
                          Animation<double> animation,
                          Animation<double> secondaryAnimation) {
                        /// 嵌套一层渐变动画
                        return FadeTransition(
                            opacity: animation,
                            /// 渐变动画+滑动动画
                            child: SlideTransition(
                              position: Tween<Offset>(
                                begin: const Offset(1.0, 0.0),
                                end: const Offset(0.0, 0.0),
                              ).animate(animation),

                              ///  child导航的第二个页面
                              child: SecondRoute(),
                            ));
                      }));
            },
            // 按钮显示内容
            child: Text("进入第二页"),
          )
        ],
      ),
    );
  }
}
相关推荐
茶卡盐佑星_6 小时前
VUE的基础指令介绍
前端·vue.js·flutter
张风捷特烈6 小时前
FlutterUnit & TolyUI | 布局游乐场
android·flutter
长亭外的少年20 小时前
深入解析三大跨平台开发框架:Flutter、React Native 和 uniapp
flutter·react native·uni-app
鳄鱼不怕_牙医不怕20 小时前
Flutter 源码梳理系列(六):ProxyWidget、InheritedWidget、ParentDataWidget
flutter·源码阅读
小红星闪啊闪2 天前
Flutter的主题
前端·flutter
sg_knight2 天前
如何在dart中实现单例模式
flutter·单例模式·设计模式·dart
程序员老刘·2 天前
flutter是app跨平台最优解吗?
android·flutter·ios
生活、追梦者3 天前
【vuejs】directive 自定义指令的详解和使用总结
前端·vue.js·flutter
周成风4 天前
Flutter学习目录
学习·flutter
nicepainkiller4 天前
Flutter Navigator.popUntil 参数传递
前端·flutter