flutter路由简析

一.什么是flutter的路由

路由是用于页面跳转的一种方式,方便管理页面之间的跳转和互相传递数据,进行交互。使用路由,我们可以轻松实现从一个页面转换到另一个页面,系统底层其实是在帮我们将小部件执行入栈出栈操作。

在Android中,我们开启一个新页面是Activity。在iOS中,我们开启一个新页面是ViewControllers。而在Flutter中,每一个页面都是小部件, 在Flutter中我们是使用了Navigator的Api实现了页面的跳转与交互。

Navigator的Api在flutter的1.22的版本中在之前的基础上进行了Navigator 2.0的更新,引入了一套全新的声明式 API来实现路由的交互。

二.flutter路由的原理

用flutter开发界面最离不开的就是路由器,只要是涉及界面跳转就需要路由功能,而flutter的ui组成全部都是widget,在布局的时候我们并没有直接使用到路由部件(Navigator),那么它是在什么时候起作用的呢,通过翻看源码可以发现是从布局的根部件MaterialApp开始一步一步引入的。

一般app的根部件为MaterialApp,而它的build方法构建了WidgetsApp部件。

Widget build(BuildContext context) {
    Widget result = _buildWidgetApp(context);
		...
    return ScrollConfiguration(
      behavior: _MaterialScrollBehavior(),
      child: HeroControllerScope(
        controller: _heroController,
        child: result,
      )
    );
  }
Widget _buildWidgetApp(BuildContext context) {
  	...
  	return WidgetsApp(
      key: GlobalObjectKey(this),
      navigatorKey: widget.navigatorKey,
      navigatorObservers: widget.navigatorObservers!,
      pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) {
        return MaterialPageRoute<T>(settings: settings, builder: builder);
      },
      ...
    );
  }
复制代码

WidgetsApp部件的build的方法会构建Navigator路由部件,要显示的内容部件都会成为Navigator的孩子部件,我们要跳转的操作一般都会用Navigator.push等方法。

  Widget build(BuildContext context) {
  	Widget? routing;
    if (_usesRouter) {
      ...
    } else if (_usesNavigator) {
      assert(_navigator != null);
      routing = Navigator(
        restorationScopeId: 'nav',
        key: _navigator,
        initialRoute: _initialRouteName,
        ...
      );
    }
  }
复制代码

而Navigator部件的的build方法则最终由Overlay来显示路由的界面。

Widget build(BuildContext context) {
    ...
    return HeroControllerScope.none(
      child: Listener(
        			...
              child: Overlay(
                key: _overlayKey,
                initialEntries: _initialOverlayEntries,
        ),
      ),
    );
  }
复制代码

而Overlay部件又通过路由器存储了多少个界面将界面以_OverlayEntry部件的形式加入到onstageChildren(需要绘制的路由) 集合中,而offstageChildren 是不需要绘制的部件集合。

Widget build(BuildContext context) {
    final List<Widget> children = <Widget>[];
    bool onstage = true;
    int onstageCount = 0;
    for (int i = _entries.length - 1; i >= 0; i -= 1) {
      final OverlayEntry entry = _entries[i];
      if (onstage) {
        onstageCount += 1;
        children.add(_OverlayEntryWidget(
          key: entry._key,
          entry: entry,
        ));
        if (entry.opaque)
          onstage = false;
      } else if (entry.maintainState) {
        children.add(_OverlayEntryWidget(
          key: entry._key,
          entry: entry,
          tickerEnabled: false,
        ));
      }
    }
    /// Special version of a [Stack], that doesn't layout and render the 						first [skipCount] children.
		/// The first [skipCount] children are considered "offstage".
    return _Theatre(
      skipCount: children.length - onstageCount,
      children: children.reversed.toList(growable: false),
      clipBehavior: widget.clipBehavior,
    );
  }
复制代码

也就是说你的MaterialApp的home属性显示的就是当前的路由界面,如果你需要跳转到路由B界面的话,B界面会显示在home界面的上面,也就是说把home界面当做路由A,路由B界面会显示在A界面上面,所以A界面就被B界面覆盖了,从而起到了跳转的作用,这是因为路由A界面和路由B界面的父部件是Stack。

默认的第一个路由界面的获取,是在Navigator的defaultGenerateInitialRoutes方法里面实现的。

const Navigator({
   	...
    this.initialRoute,
    this.onGenerateInitialRoutes = Navigator.defaultGenerateInitialRoutes,
    ...
  }
  
//initialRouteName = widget.initialRoute ?? Navigator.defaultRouteName;
static List<Route<dynamic>> defaultGenerateInitialRoutes(NavigatorState navigator, String initialRouteName) {
    final List<Route<dynamic>?> result = <Route<dynamic>?>[];
    if (initialRouteName.startsWith('/') && initialRouteName.length > 1) {
      initialRouteName = initialRouteName.substring(1); // strip leading '/'
     	...
      }
  	...
    return result.cast<Route<dynamic>>();
  }
  
  //defaultGenerateInitialRoutes返回值赋值给onGenerateInitialRoutes
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    ...
    if (initialRoute != null) {
        _history.addAll(
          widget.onGenerateInitialRoutes(
            this,
            widget.initialRoute ?? Navigator.defaultRouteName,
          )
        );
      }
    }
  }
复制代码

widget.initialRoute,就是我们自定义设置的启动页面,让路由管理器找到第一个需要渲染的路由界面,defaultRouteName默认值为“/”,而navigator的push方法就是向内部的_history列表加入不同的路由界面,最终将所有的路由界面放_initialOverlayEntries中交给Overlay最终显示出来。

综上所述路由的原理是Overlay部件通过Stack来实现的。

三.navigator1.0的使用说明

在 Flutter 中路由用法主要有两种用法:一种是在 MaterialApp 里的 routes 参数里配置定义好路由列表,也就是提前定义好要跳转的页面和名称,然后通过这个名称来执行跳转。另一种则是通过onGenerateRoute来设置路由跳转规则实现页面跳转交互,并可通过navigatorObservers实现路由的监听。

Route 在 Flutter 中主要有两种实现方法:一个是使用 MaterialPageRoute;另一个是使用 PageRouteBuilder 来构建。

MaterialPageRoute({
    // 构建页面
    @required this.builder,
    // 路由设置
    RouteSettings settings,
    // 是否保存页面状态、内容
    this.maintainState = true,
    bool fullscreenDialog = false,
  })
复制代码
PageRouteBuilder({
    // 路由设置
    RouteSettings settings,
    // 目标页面
    @required this.pageBuilder,
    // 跳转过度动画设置
    this.transitionsBuilder = _defaultTransitionsBuilder,
    this.transitionDuration = const Duration(milliseconds: 300),
    ...
  })
复制代码

使用PageRouteBuilder 构建不仅可以实现路由的基本配置还可以设置跳转动画效果。

页面跳转传递参数,不直接使用在 MaterialApp 的 routes 属性里静态定义的属性值而是使用页面参数配置的动态路由方法,并通过then或者await取得参数。

// 关闭页面返回数据
Navigator.pop(context, '返回数据');
// 接收返回数据
Navigator.push<String>(context,
        MaterialPageRoute(builder: (BuildContext context) {
      return ButtonSamples(title: '标题', name: '名称');
    })).then((String result) {
      //处理代码
    });
复制代码

四.Navigator2.0的简述

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

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享