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("进入第二页"),
          )
        ],
      ),
    );
  }
}
相关推荐
wuzuyu3657 小时前
用php做一个简易的路由
php·路由
ALLIN16 小时前
Flutter 三种方式实现页面切换后保持原页面状态
flutter
Dabei17 小时前
Flutter 国际化
flutter
Dabei17 小时前
Flutter MQTT 通信文档
flutter
Dabei20 小时前
Flutter 中实现 TCP 通信
flutter
孤鸿玉20 小时前
ios flutter_echarts 不在当前屏幕 白屏修复
flutter
前端 贾公子1 天前
《Vuejs设计与实现》第 16 章(解析器) 上
vue.js·flutter·ios
tangweiguo030519871 天前
Flutter 数据存储的四种核心方式 · 从 SharedPreferences 到 SQLite:Flutter 数据持久化终极整理
flutter
0wioiw01 天前
Flutter基础(②④事件回调与交互处理)
flutter
肥肥呀呀呀1 天前
flutter配置Android gradle kts 8.0 的打包名称
android·flutter