围观 MaterialApp, Navigator, Route, OverlayEntry, Overlay 合伙'直播' Widget

围观 MaterialApp, Navigator, Route, OverlayEntry, Overlay 合伙'直播' Widget

自己写的widget是怎么被展示的?Navigator是怎么切换页面的?Navigator、Route、OverlayEntry都有啥联系?
从一个简单例子通过源码看这些组件怎么合伙‘直播’Widget的。 基于flutter版本1.12.13+hotfix.8。

简单例子:

main.dart

runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: appName,
        home: HomePage(),
        navigatorObservers: [myNavigatorObserver],
        showPerformanceOverlay: true
        ...
    );
  }
}

MaterialApp是啥?

material/app.dart

/// An application that uses material design.
class MaterialApp extends StatefulWidget {
    @override
    _MaterialAppState createState() => _MaterialAppState();
}

class _MaterialAppState extends State<MaterialApp> {
    List<NavigatorObserver> _navigatorObservers;  // 保存了外面传的NavigatorObserver
    
    void initState() {
        _navigatorObservers = List<NavigatorObserver>.from(widget.navigatorObservers)
            ..add(_heroController);
    }

    Widget build(BuildContext context) {
        Widget result = WidgetsApp(       // 借用了WidgetsApp并传递所有属性值
           key: GlobalObjectKey(this),
          navigatorKey: widget.navigatorKey,
            navigatorObservers: _navigatorObservers,
            pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) {  // 初始化了pageRouteBuilder
                return MaterialPageRoute<T>(settings: settings, builder: builder);
            },
            home: widget.home,
            routes: widget.routes,
            ...    // 把MaterialApp的参数都传递了
        );
        return ScrollConfiguration(   // 
            child: result,
        );
    }
}

就是说 MaterialApp 的内核是 WidgetsApp。

WidgetsApp是啥?

widgets/app.dart

/// wraps a number of widgets that are commonly required for an application.
class WidgetsApp extends StatefulWidget {
}

class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
    GlobalKey<NavigatorState> _navigator;
    initState() {
        _navigator = widget.navigatorKey ?? GlobalObjectKey<NavigatorState>(this);  // GlobalObjectKey
        WidgetsBinding.instance.addObserver(this);  // 注册了自己(WidgetsBindingObserver)监听如didPopRoute、didPushRoute、didChangeLocales
    }

    Widget build(BuildContext context) {
        Widget navigator = Navigator(     // 包装了Navigator !
            key: _navigator,       // key是_navigator
            initialRoute: '/'      //  就是home属性指代的widget
            onGenerateRoute: _onGenerateRoute,
            onUnknownRoute: _onUnknownRoute,
            observers: widget.navigatorObservers,
        );

        Widget result = navigator;
        // 下面根据widget的属性值装饰result,如builder、textStyle、performanceOverlay、onGenerateTitle等
        Widget title = title = Title(  // 
            title: widget.title,
            color: widget.color,
            child: result
        );
        return Shortcuts(
            shortcuts: _keyMap,
            child: Actions(
                actions: _actionMap,
                child: DefaultFocusTraversal(
                policy: ReadingOrderTraversalPolicy(),
                child: _MediaQueryFromWindow( // _MediaQueryFromWindowsState with了WidgetsBindingObserver,监听window的属性变化、系统字体大小变化做rebuild
                         child: Localizations(
                        locale: appLocale,
                        delegates: _localizationsDelegates.toList(),
                            child: title,
                        )
                    )
                )
            )
        );
    }

    Route<dynamic> _onGenerateRoute(RouteSettings settings) {
        // 检查settings.name是'/'或者widget.routes里的,是就用widget.pageRouteBuilder(WidgetsApp默认是MaterialPageRoute)包装widget.home
        // 这个widget生成route,否则用widget.onGenerateRoute生成route。
    }
}

就是说 WidgetsApp 里包装了Navigator,传递了初始route名,还监听了window的属性变化、系统字体大小变化来rebuild自己。

Navigator是啥?

widgets/navigator.dart

/// A widget that manages a set of child widgets with a stack discipline.
/// [Navigator.of] operates on the nearest ancestor [Navigator] from the given
/// [BuildContext]. Be sure to provide a [BuildContext] below the intended
/// [Navigator].
class Navigator extends StatefulWidget {
    static NavigatorState of(   // 通过of()获取离context最近的Navigator对象的NavigatorState,要确保context是Navigator的子孙节点
        BuildContext context, {
        bool rootNavigator = false,
        bool nullOk = false,
    }) {
        final NavigatorState navigator = rootNavigator
            ? context.findRootAncestorStateOfType<NavigatorState>()
            : context.findAncestorStateOfType<NavigatorState>();
        return navigator;
    }
    
    static Future<T> push<T extends Object>(BuildContext context, Route<T> route) { // 各种push、pop api都是调用Navigator.of().XXX
    return Navigator.of(context).push(route);
  }
}

class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
    final GlobalKey<OverlayState> _overlayKey = GlobalKey<OverlayState>();
    final List<Route<dynamic>> _history = <Route<dynamic>>[];
    final Set<Route<dynamic>> _poppedRoutes = <Route<dynamic>>{};

    /// The [FocusScopeNode] for the [FocusScope] that encloses the routes.
    final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: 'Navigator Scope');

    final List<OverlayEntry> _initialOverlayEntries = <OverlayEntry>[];
    OverlayState get overlay => _overlayKey.currentState;
    
    initState() {
        Route<Object> route;
        route ??= _routeNamed<Object>(Navigator.defaultRouteName, arguments: null);
        push(route);    // push初始route , 会让route创建overlay entries,并保存在_history里,后面会再分析。
        for (Route<dynamic> route in _history)
            _initialOverlayEntries.addAll(route.overlayEntries);  // 对于'/'route来说,_history也是有元素的。
    }

    // pushNamed、pushReplacement、pushReplacementNamed、popAndPushNamed、pushNamedAndRemoveUntil、pushAndRemoveUntil等逻辑类似push
    Future<T> push<T extends Object>(Route<T> route) {   // route可以自定义产生,借助MaterialPageRoute或PageRouteBuilder。
        route._navigator = this;  // 把NavigatorState绑定到route,在后面install(OverlayEntry)里用到。
        route.install(_currentOverlayEntry);  // 把route放在_currentOverlayEntry之上,_currentOverlayEntry是最新route里最后那个OverlayEntry。
        _history.add(route);  // 保存route !
        route.didPush();  // 执行 focusScopeNode.requestFocus();
        for (NavigatorObserver observer in widget.observers) {  // 通过NavigatorObserver通知route pushed
            observer.didPush(route, oldRoute);
        }
    }

    bool pop<T extends Object>([ T result ]) {
        final Route<dynamic> route = _history.last;
        if (route.didPop(result ?? route.currentResult)) {
            if (_history.length > 1) {
                _history.removeLast();    // 去除顶端route
                _history.last.didPopNext(route);
                for (NavigatorObserver observer in widget.observers)
                observer.didPop(route, _history.last);    // 通过NavigatorObserver通知route poped
            }
        }
    }

    Widget build(BuildContext context) {
        return Listener(
            child: AbsorbPointer(   // 接收点击事件,还有IgnorePointer
                absorbing: false, // 会在navigation之后再设置为true (通过_overlayKey.currentContext?.findAncestorRenderObjectOfType()找到该absorber)
                child: FocusScope(
                node: focusScopeNode,
                autofocus: true,
                child: Overlay(   // 包装了一个Overley
                        key: _overlayKey,
                        initialEntries: _initialOverlayEntries,
                ),
                ),
            ),
        );
    }
}

Overlay是啥?

/// A [Stack] of entries that can be managed independently.
class Overlay extends StatefulWidget {
    final List<OverlayEntry> initialEntries;
    OverlayState _overlay;

    static OverlayState of(BuildContext context, { Widget debugRequiredFor }) {
        final OverlayState result = context.findAncestorStateOfType<OverlayState>();
        return result;
    }
}

OverlayState extends State<Overlay> with TickerProviderStateMixin {
    final List<OverlayEntry> _entries = <OverlayEntry>[];  // 所有OverlayEntry,通过insertAll、insert、_remove、rearrange操作增删改。

    initState() {
        insertAll(widget.initialEntries);
    }

    Widget build(BuildContext context) {
        final List<Widget> onstageChildren = <Widget>[];   // 保存可在屏幕上可见的widget 
        final List<Widget> offstageChildren = <Widget>[];  // 保存在屏幕上不可见但保活的widget 
        bool onstage = true;
        for (int i = _entries.length - 1; i >= 0; i -= 1) {  // 反向遍历_entries(从新到旧)区分哪些entries是可见,如果哪个声明了不透明,底部的entries就都是不可见的了。
            final OverlayEntry entry = _entries[i];
            if (onstage) {
                onstageChildren.add(_OverlayEntry(entry));  // OverlayEntry不是widget,把它包装成widget _OverlayEntry,会调用entry的builder创建widget。
                if (entry.opaque)
                onstage = false;
            } else if (entry.maintainState) {
                offstageChildren.add(TickerMode(enabled: false, child: _OverlayEntry(entry)));
            }
        }
        return _Theatre(  // 马上要直播了,widget是OverlayEntry,我们写的真widget在哪呢??
            onstage: Stack(   // visible
                fit: StackFit.expand,
                children: onstageChildren.reversed.toList(growable: false), // 按从底层到上层的顺序绘制widget
            ),
            offstage: offstageChildren,  // kept alive, and are built, but are not laid out or painted.
        );
    }

    insert(OverlayEntry entry, { OverlayEntry below, OverlayEntry above }) {
        entry._overlay = this;   // insert时把当前OverlayState关联到OverlayEntry
        setState(() {    // setState !!
            _entries.insert(_insertionIndex(below, above), entry);
        });
    }

    void _remove(OverlayEntry entry) {
        if (mounted) {
            setState(() {
                _entries.remove(entry);
            });
        }
    }
    
    rearrange();
}


class _OverlayEntry extends StatefulWidget {
  final OverlayEntry entry;
}

class _OverlayEntryState extends State<_OverlayEntry> {
  @override
  Widget build(BuildContext context) {
    return widget.entry.builder(context);   // 调用了OverlayEntry的builder !
  }

  void _markNeedsBuild() {
    setState(() { /* the state that changed is in the builder */ });
  }
}

有2个问题:
1、OverlayState是怎么显示MaterialApp里的home参数指定的widget(初始route)呢?
2、_Theatre准备好了,马上要直播了,可widget是OverlayEntry,我们写的真widget在哪呢?

第1个问题,再回看下源码,再看看NavigatorState里push初始route时干了啥。
在initState里先生成route、再push(route)、再遍历_history放到_initialOverlayEntries里:

route ??= _routeNamed<Object>(Navigator.defaultRouteName, arguments: null);
push(route); 
for (Route<dynamic> route in _history)
    _initialOverlayEntries.addAll(route.overlayEntries);

先看看生成的是啥route:

Route<T> _routeNamed<T>(String name, { @required Object arguments, bool allowNull = false }) {
    Route<T> route = widget.onGenerateRoute(settings);
    return route;
}

onGenerateRoute就是_WidgetsAppState里创建Navigator时赋值的方法_onGenerateRoute,这里对于'/'route的就是调用widget.pageRouteBuilder返回route。
    final Route<dynamic> route = widget.pageRouteBuilder<dynamic>( 
        settings,
        pageContentBuilder,  // 对于'/'route就是返回widget.home
    );
    return route;

这里widget.pageRouteBuilder就是_MaterialAppState里传递的,实际就是MaterialPageRoute<T>(settings: settings, builder: builder)。
对于widget.home 这个widget,生成的是MaterialPageRoute。
先列下MaterialPageRoute的父类与主要方法:

class MaterialPageRoute<T> extends PageRoute<T> {
}

abstract class PageRoute<T> extends ModalRoute<T> {
}

abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T> {
    @override
    void install(OverlayEntry insertionPoint) {
        super.install(insertionPoint);    // 最后调用超父类OverlayRoute的install
        ...
    }
    
    @override
    Iterable<OverlayEntry> createOverlayEntries() sync* {  // 同步生成器,生成2个OverlayEntry并返回
        yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);  
        yield OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
    }
}

OverlayEntry不是widget,参数builder是_buildModalScope会创建widget,里面包含用route.buildPage()返回的widget。这个buildPage在MaterialPageRoute里有实现,就是返回widget.home 。

abstract class TransitionRoute<T> extends OverlayRoute<T> {
}

abstract class OverlayRoute<T> extends Route<T> {
    Iterable<OverlayEntry> createOverlayEntries();
    final List<OverlayEntry> _overlayEntries = <OverlayEntry>[];
    
    @override
    void install(OverlayEntry insertionPoint) {
        _overlayEntries.addAll(createOverlayEntries());  // 先创建OverlayEntries在add进_overlayEntries里
        navigator.overlay?.insertAll(_overlayEntries, above: insertionPoint);  // 把_overlayEntries插入route关联的overlay里。对于'/'route,overlay此时还是null。
        super.install(insertionPoint);
    }
}

/// An abstraction for an entry managed by a [Navigator].
abstract class Route<T> {
    final RouteSettings settings;
    List<OverlayEntry> get overlayEntries => const <OverlayEntry>[];
}

push(route)里有这2句:

route.install(_currentOverlayEntry);   // 把route插入到_currentOverlayEntry之上。
_history.add(route);

简单总结,对于widget.home这个widget:

  • 对应的route是MaterialPageRoute,是MaterialApp的state里自动配置的;
  • 在NavigatorState的initState()里,把这个route插入到当前最新的route的最新的overlayentry之上(应该是null)。在插入同时,让route自己创建overlay entries,其中一个overlayEntry提供方法返回widget.home;
  • 在NavigatorState的_history里保存了这个route,并遍历_history把所有route里的overlay entry保存到_initialOverlayEntries;
  • 在NavigatorState的build()里,创建Overlay并传递了_initialOverlayEntries;
  • 在OverlayState的initState()里把_initialOverlayEntries放进_entries里;
  • 在OverlayState的build()里遍历_entries,区分要显示的和不用绘制的entry,包装成_OverlayEntry这个widgte给_Theatre去渲染;

第2个问题,前面看到widget其实是被包装在route的其中一个OverlayEntry里,OverlayEntry不是widget,其参数builder会创建widget,里面包含用route.buildPage()返回的widget,即home widget。route.buildPage()的调用时机就是 OverlayState在进行insertAll、insert、_remove等操作时会setState,这会导致子孙节点做build。

以上分析的是MaterialApp了里home参数的widget的绘制路径,其他widget页面可以通过Navigator.of()获得NavigatorState再push、pop一个route,可以是命名方式的route(会用pageRouteBuilder生成route),也可以自己实现生成那个route:

route = PageRouteBuilder<dynamic>(
    settings: RouteSettings(name: path),
    transitionDuration: Duration(milliseconds: 200),
    pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
        return findPage(path, params: params);     // 根据path找到对应的widget页面
    },
    transitionsBuilder: _standardTransitionsBuilder(transition),
);

OverlayEntry是啥?

class OverlayEntry {
    final WidgetBuilder builder;
    bool _opaque;
    bool _maintainState;
    OverlayState _overlay;   // 直接关联了overlay
    final GlobalKey<_OverlayEntryState> _key = GlobalKey<_OverlayEntryState>();
    
    void markNeedsBuild() {  // 让OverlayEntry在下一次渲染周期做build,其实是调用了builder属性
        _key.currentState?._markNeedsBuild();
  }
}

简单总结:

MaterialApp 链式包装了 WidgtesApp 包装了 Navigator 包装了 Overlay ,这些都是widget,
为了方便管理页面,抽象出了Route、OverlayEntry,Navigator管理所有Route,一个route包含多个OverlayEntry,OverlayEntry提供builder对最终的widget做build。
widget页面可以通过Navigator进行push、pop,都是在栈顶操作,操作完会通知各NavigationObserver发生页面切换。

借鉴点:

通过key找到currentContext、currentWidget、currentState,如:

class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
    final List<Route<dynamic>> _history = <Route<dynamic>>[];
    final GlobalKey<OverlayState> _overlayKey = GlobalKey<OverlayState>();
    OverlayState get overlay => _overlayKey.currentState;

    @override
    Widget build(BuildContext context) {
    return Listener(
      child: AbsorbPointer(
        child: FocusScope(
          child: Overlay(
            key: _overlayKey
          ),
        ),
      ),
    );
  }
}

class Overlay extends StatefulWidget {}

class OverlayState extends State<Overlay> with TickerProviderStateMixin {
    void insertAll() {}
}

abstract class OverlayRoute<T> extends Route<T> {
    void install(OverlayEntry insertionPoint) {
        ...
        navigator.overlay?.insertAll(_overlayEntries, above: insertionPoint);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354