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 小时前
Flutter for OpenHarmony:mockito 单元测试的替身演员,轻松模拟复杂依赖(测试驱动开发必备) 深度解析与鸿蒙适配指南
服务器·驱动开发·安全·flutter·华为·单元测试·harmonyos
念格9 小时前
Flutter 弹窗 UI 不刷新?用 StatefulBuilder 解决
flutter
程序员老刘11 小时前
2026春招Flutter岗位为何变少?我看到的3个招聘逻辑变化
flutter·ai编程·客户端
念格11 小时前
Flutter 实现点击任意位置收起键盘的最佳实践
flutter
念格11 小时前
Flutter ListView Physics 滚动物理效果详解
flutter
国医中兴12 小时前
ClickHouse的数据模型设计:从理论到实践
flutter·harmonyos·鸿蒙·openharmony
国医中兴14 小时前
ClickHouse数据导入导出最佳实践:从性能到可靠性
flutter·harmonyos·鸿蒙·openharmony
国医中兴15 小时前
大数据处理的性能优化技巧:从理论到实践
flutter·harmonyos·鸿蒙·openharmony
●VON16 小时前
Flutter 入门指南:从基础组件到状态管理核心机制
前端·学习·flutter·von
西西学代码16 小时前
Flutter---SingleChildScrollView
前端·javascript·flutter