Flutter路由源码(Navigator原理)详解

Navigator.of(context).pushNamed

用flutter开发界面最离不开的就是路由器,你只要需要跳转到其他界面就需要路由功能,而flutter的ui组成全部都是widget,在布局的时候我们没有用路由的小部件(Navigator),那么它是怎么起到作用的呢,肯定是隐藏在我们用的根部件里面,一般我们布局的根部件为MaterialApp小部件,MaterialApp小部件的build方法构建了WidgetsApp小部件

Widget build(BuildContext context) {
    Widget result = WidgetsApp(
      key: GlobalObjectKey(this),
      navigatorKey: widget.navigatorKey,
      navigatorObservers: _navigatorObservers,
      pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) {
        return MaterialPageRoute<T>(settings: settings, builder: builder);
      },
.....省略若干行
      }

WidgetsApp小部件的build的方法会构建Navigator路由小部件,要显示的内容小部件都会成为Navigator的孩子部件,例如home制定小部件会成为Navigator部件的孩子部件,而我们要跳转的操作一般都会用

Navigator.push
Navigator.of(context).pushNamed

等。

 

Widget build(BuildContext context) {
    Widget navigator;
    if (_navigator != null) {
      navigator = Navigator(
        key: _navigator,
        // If window.defaultRouteName isn't '/', we should assume it was set
        // intentionally via `setInitialRoute`, and should override whatever
        // is in [widget.initialRoute].
        initialRoute: WidgetsBinding.instance.window.defaultRouteName != Navigator.defaultRouteName
            ? WidgetsBinding.instance.window.defaultRouteName
            : widget.initialRoute ?? WidgetsBinding.instance.window.defaultRouteName,
        onGenerateRoute: _onGenerateRoute,
        onUnknownRoute: _onUnknownRoute,
        observers: widget.navigatorObservers,
      );
    }
.....省略若干行
}

 

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

 Widget build(BuildContext context) {
    assert(!_debugLocked);
    assert(_history.isNotEmpty);
    return Listener(
      onPointerDown: _handlePointerDown,
      onPointerUp: _handlePointerUpOrCancel,
      onPointerCancel: _handlePointerUpOrCancel,
      child: AbsorbPointer(
        absorbing: false, // it's mutated directly by _cancelActivePointers above
        child: FocusScope(
          node: focusScopeNode,
          autofocus: true,
          child: Overlay(
            key: _overlayKey,
            initialEntries: _initialOverlayEntries,
          ),
        ),
      ),
    );
  }

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

Widget build(BuildContext context) {
    // These lists are filled backwards. For the offstage children that
    // does not matter since they aren't rendered, but for the onstage
    // children we reverse the list below before adding it to the tree.
    final List<Widget> onstageChildren = <Widget>[];
    final List<Widget> offstageChildren = <Widget>[];
    bool onstage = true;
    for (int i = _entries.length - 1; i >= 0; i -= 1) {
      final OverlayEntry entry = _entries[i];
      if (onstage) {
        onstageChildren.add(_OverlayEntry(entry));
//如果不透明的话,下面的布局不用显示了
        if (entry.opaque)
          onstage = false;
      } else if (entry.maintainState) {
//不需要渲染的路由小部件
        offstageChildren.add(TickerMode(enabled: false, child: _OverlayEntry(entry)));
      }
    }
    return _Theatre(
      onstage: Stack(
        fit: StackFit.expand,
        children: onstageChildren.reversed.toList(growable: false),
      ),
      offstage: offstageChildren,
    );
  }

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

接下来来看一下默认的第一个路由界面的获取,这个获取是在NavigatorStateinitState方法里面实现的

void initState() {
    super.initState();
    for (NavigatorObserver observer in widget.observers) {
      assert(observer.navigator == null);
      observer._navigator = this;
    }
    String initialRouteName = widget.initialRoute ?? Navigator.defaultRouteName;
    if (initialRouteName.startsWith('/') && initialRouteName.length > 1) {
      initialRouteName = initialRouteName.substring(1); // strip leading '/'
      assert(Navigator.defaultRouteName == '/');
      final List<Route<dynamic>> plannedInitialRoutes = <Route<dynamic>>[
        _routeNamed<dynamic>(Navigator.defaultRouteName, allowNull: true, arguments: null),
      ];
      final List<String> routeParts = initialRouteName.split('/');
      if (initialRouteName.isNotEmpty) {
        String routeName = '';
        for (String part in routeParts) {
          routeName += '/$part';
          plannedInitialRoutes.add(_routeNamed<dynamic>(routeName, allowNull: true, arguments: null));
        }
      }
      if (plannedInitialRoutes.last == null) {
       
        push(_routeNamed<Object>(Navigator.defaultRouteName, arguments: null));
      } else {
        plannedInitialRoutes.where((Route<dynamic> route) => route != null).forEach(push);
      }
    } else {
      Route<Object> route;
      if (initialRouteName != Navigator.defaultRouteName)
        route = _routeNamed<Object>(initialRouteName, allowNull: true, arguments: null);
      route ??= _routeNamed<Object>(Navigator.defaultRouteName, arguments: null);
      push(route);
    }
    for (Route<dynamic> route in _history)
      _initialOverlayEntries.addAll(route.overlayEntries);
  }

widget.initialRouteinitialRoute属性大家应该比较熟吧,就是设置一个字符串,好让路由管理器找到第一个需要渲染的路由界面

defaultRouteName默认值为“/”,而push方法就是向_history加入不同的路由界面,最终将所有的路由界面放_initialOverlayEntries中交给Overlay最终显示出来。

_routeNamed方法是用来获得路由界面的,代码如下

 Route<T> _routeNamed<T>(String name, { @required Object arguments, bool allowNull = false }) {
    
    final RouteSettings settings = RouteSettings(
      name: name,
      isInitialRoute: _history.isEmpty,
      arguments: arguments,
    );
//调用onGenerateRoute获得路由器界面
    Route<T> route = widget.onGenerateRoute(settings);
    if (route == null && !allowNull) {
    //获得不到就调用onUnknownRoute获得
      route = widget.onUnknownRoute(settings);
     
    }
    return route;
  }

onGenerateRouteonUnknownRoute就是我们配置的MaterialApp的两个方法属性

MaterialApp(
          onUnknownRoute: Router.generateRoute ,
            title: 'Flutter Unit',
            debugShowCheckedModeBanner: false,
            onGenerateRoute: Router.generateRoute,
            theme: ThemeData(
              primarySwatch: state.themeColor,
              fontFamily: state.fontFamily,
            ),
            home: UnitSplash()),
      )

在调用onGenerateRoute之前先调用的_onGenerateRoute,如下所示

Route<dynamic> _onGenerateRoute(RouteSettings settings) {
    final String name = settings.name;
    final WidgetBuilder pageContentBuilder = name == Navigator.defaultRouteName && widget.home != null
        ? (BuildContext context) => widget.home
        : widget.routes[name];

    if (pageContentBuilder != null) {
        final Route<dynamic> route = widget.pageRouteBuilder<dynamic>(
        settings,
        pageContentBuilder,
      );
      return route;
    }
    if (widget.onGenerateRoute != null)
      return widget.onGenerateRoute(settings);
    return null;
  }

如果home属性不为null的话就会给把home作为路由的首界面,如果为null就会调用onGenerateRoute方法决定首个路由界面是哪一个。

再来看一下Navigator.of(context).pushNamed进入新的界面的方法,路由管理类的真正实现都是在NavigatorState中,

 

Future<T> push<T extends Object>(Route<T> route) {

    final Route<dynamic> oldRoute = _history.isNotEmpty ? _history.last : null;
    route._navigator = this;
//真正实现界面的更新
    route.install(_currentOverlayEntry);
//将新的路由界面加入到历史集合中
    _history.add(route);
//调用新的route的生命周期
    route.didPush();
    route.didChangeNext(null);
    if (oldRoute != null) {
//调用老的route的生命周期
      oldRoute.didChangeNext(route);
      route.didChangePrevious(oldRoute);
    }
    for (NavigatorObserver observer in widget.observers)
      observer.didPush(route, oldRoute);
    RouteNotificationMessages.maybeNotifyRouteChange(_routePushedMethod, route, oldRoute);
   
    _afterNavigation(route);
    return route.popped;
  }

其实Route类就是一个包装类,一是用来处理生命周期方法的,二是用来build小部件返回给Overlay最终显示在Stack小部件上,继续看引起重新渲染的route.install方法,install是在OverlayRoute中实现的

void install(OverlayEntry insertionPoint) {
    assert(_overlayEntries.isEmpty);
    _overlayEntries.addAll(createOverlayEntries());
    navigator.overlay?.insertAll(_overlayEntries, above: insertionPoint);
    super.install(insertionPoint);
  }
 navigator.overlay?.insertAll(_overlayEntries, above: insertionPoint);

其中 navigator就是我们的路由管理器,而insertAll就是向Overlay小部件的实现类OverlayState

 void insertAll(Iterable<OverlayEntry> entries, { OverlayEntry below, OverlayEntry above }) {
    
    if (entries.isEmpty)
      return;
    for (OverlayEntry entry in entries) {
      assert(entry._overlay == null);
      entry._overlay = this;
    }
    setState(() {
      _entries.insertAll(_insertionIndex(below, above), entries);
    });
  }
也就是说最终显示的小部件navigator导航器的Overlay小部件,调用setState方法引起Overlay下的孩子重新渲染,它的孩子就包括我们的路由界面。这里还要注意一下createOverlayEntries方法
Iterable<OverlayEntry> createOverlayEntries() sync* {
    yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
    yield OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
  }

返回了两个OverlayEntry,第一个为遮罩层,第二个为我们要定义的路由界面,遮罩常用于对话框,其实flutter

的对话框就是走的路由的原理,还有通过Overlay来创造提示框或悬浮框也是走的这个路由的原理,或者说路由的原理就是Overlay小部件通过Stack来实现的。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值