Android开发Flutter 入门与实战

实例解析山路十八弯的Flutter 2.0路由

2021-12-11  本文已影响0人  岛上码农

前言

上一篇Flutter 2.0的路由把我搞懵了对 Flutter 2.0的路由做了介绍,看完介绍基本上还是云里雾里。折腾了一天,终于把一个完整的示例弄出来了,一句话总结就是:山路十八弯!

山路十八弯
提示一下,本文篇幅较长,阅读比较耗时(走山路肯定时间久),如果没耐心,点个赞直接下载源码看也是可以的。 点个赞吧

代码结构

为了简化理解,本篇将之前的多余的演示去掉了,只保留了启动页,动态列表和动态详情页面,源码可以看这里本篇源码地址。具体而言代码分三种:

代码目录结构如下:


代码结构

2.0路由的理念

2.0路由之所以要改动,更多地是为了满足 Web 端复杂路由的需要,同时也是满足状态驱动界面设计的理念。即界面与行为进行分离,通过更改状态来驱动界面完成既定行为。因此,2.0路由最关键的地方就是之前的 Navigator.pushNavigator.pop 方法在新的界面中不见了,界面只是响应用户操作去更改数据状态,而页面路由跳转统一交给了 RougterDelegate 来完成。

路由代码解读

为了简化代码阅读,路由配置相关的代码都在 app_router_path.dart 类中。这里定义了如下内容:

这部分代码并不复杂,阅读源码即可。复杂之处在于路由委托实现类,在 router_delegate.dart定义。整个类的代码如下:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:home_framework/dynamic_detail.dart';
import 'package:home_framework/models/dynamic_entity.dart';
import 'package:home_framework/not_found.dart';
import 'package:home_framework/routers/app_router_path.dart';
import 'package:home_framework/splash.dart';

import '../dynamic.dart';

class AppRouterDelegate extends RouterDelegate<AppRouterConfiguration>
    with
        ChangeNotifier,
        PopNavigatorRouterDelegateMixin<AppRouterConfiguration> {
  @override
  final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

  RouterPaths _routerPath;
  get routerPath => _routerPath;
  set routerPath(RouterPaths value) {
    if (_routerPath == value) return;
    _routerPath = value;

    notifyListeners();
  }

  dynamic _state;
  get state => _state;

  bool _splashFinished = false;
  get splashFinished => _splashFinished;

  set splashFinished(bool value) {
    if (_splashFinished == value) return;
    _splashFinished = value;

    notifyListeners();
  }

  @override
  Widget build(BuildContext context) {
    return Navigator(
      key: navigatorKey,
      pages: _buildPages(),
      onPopPage: _handlePopPage,
    );
  }

  List<Page<void>> _buildPages() {
    if (_splashFinished) {
      return [
        MaterialPage(
            key: ValueKey('home'),
            child: DynamicPage(_handleDynamicItemChanged)),
        if (_routerPath == RouterPaths.splash)
          MaterialPage(
              key: ValueKey('splash'), child: Splash(_handleSplashFinished)),
        if (_routerPath == RouterPaths.dynamicDetail)
          MaterialPage(
              key: ValueKey('dynamicDetail'), child: DynamicDetail(state)),
        if (_routerPath == RouterPaths.notFound)
          MaterialPage(key: ValueKey('notFound'), child: NotFound()),
      ];
    } else {
      return [
        MaterialPage(
            key: ValueKey('splash'), child: Splash(_handleSplashFinished)),
      ];
    }
  }

  void _handleSplashFinished() {
    _routerPath = RouterPaths.dynamicList;
    _splashFinished = true;
    notifyListeners();
  }

  void _handleDynamicItemChanged(DynamicEntity dynamicEntity) {
    _routerPath = RouterPaths.dynamicDetail;
    _state = dynamicEntity;
    notifyListeners();
  }

  @override
  Future<bool> popRoute() async {
    return true;
  }

  @override
  Future<void> setNewRoutePath(AppRouterConfiguration configuration) async {
    _routerPath = configuration.path;
    _state = configuration.state;
  }

  bool _handlePopPage(Route<dynamic> route, dynamic result) {
    final bool success = route.didPop(result);
    return success;
  }

  @override
  AppRouterConfiguration get currentConfiguration =>
      AppRouterConfiguration(routerPath, state);
}

AppRouterDelegate 继承自RouterDelegate<AppRouterConfiguration>RouterDelegate本身是一个泛型类,继承时指定了使用AppRouterConfiguration实例化泛型作为路由配置类。

同时使用with方式实现了 ChangeNotifierPopNavigatorRouterDelegateMixin。其中ChangeNotifier用于增加状态更改监听对象和通知监听对象进行动作,这个监听对象的增加有底层直接完成,当有状态改变时应当调用 notifyListeners方法通知所有监听者做出相应的动作。这个相当于观察者模式的实现,有兴趣的可以看一下 ChangeNotifier 的源码。

PopNavigatorRouterDelegateMixin用于管理返回事件的,只有一个方法,可以覆盖其方法自定义返回事件。

首先看定义了成员属性:

再来看路由相关的方法(这里不包括界面传递的方法):

整个流程是当有路由配置参数改变后,会重新调用 build 方法,来构建里有页面和决定跳转到哪个页面。

业务代码变更

由于业务代码不能再实用 pushpop 跳转和返回,因此涉及到这些的都需要变更,因为需要修改路由状态参数,因此这些修改状态的行为都通过构建业务页面时传递对应的回调方法来完成。对于启动页结束的方法为:_handleSplashFinished,当启动页完成后,标记状态_splashFinishedtrue,以及修改当前的路由页面为动态列表。_handleSplashFinished方法会传递到启动页面,当启动定时时间到了之后就调用该方法来替换 push 方法,从而实现页面切换。

class Splash extends StatefulWidget {
  final Function onFinished;
  Splash(this.onFinished, {Key key}) : super(key: key);

  @override
  _SplashState createState() => _SplashState(onFinished);
}

class _SplashState extends State<Splash> {
  final Function onFinished;
  _SplashState(this.onFinished);
  bool _initialized = false;
  
  //省略其他代码
  
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    if (!_initialized) {
      _initialized = true;
      Timer(const Duration(milliseconds: 2000), () {
        onFinished();
      });
    }
  }
}

动态列表也一样,同样需要接收一个onItemTapped方法,用于响应每行元素的点击事件,并把点击的元素对象回传以更新路由参数。这里还出现了路由传递函数给动态列表,动态列表再把函数传递给每行元素的情况,是不是发现和 React 的父子组件传值有点类似?实际每个业务代码接收回调函数的目的就是为了更改路由状态参数实现页面跳转。

这里也可以看到实际上目前这种方式暴露了业务的实现,破坏了封装性,而且如果父子元素嵌套过深会导致传递链路过长。这个时候就和 React 一样,需要有类似 Redux 的状态管理器来解耦了。

App 路由配置变更

App 路由配置变更相对简单,在入口的 build 方法中返回 MaterialApp.router 方法来构建即可,这里关键的两个参数就是路由委托routerDelegate和路由信息解析器routeInformationParser,将这两个参数设置为我定义的对应类的实现对象即可。源码如下:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: '2.0路由',
      routerDelegate: AppRouterDelegate(),
      routeInformationParser: AppRouterInformationParser(),
      //省略其他代码
    );
  }
}

总结

总的来说,Flutter 2.0的路由管理相比1.0版本复杂很多,对于非 Web应用来说可以继续沿用1.0的路由。当然升级后,也有如下优点:

上一篇 下一篇

猜你喜欢

热点阅读