Flutter中的Overlay
是什麼?先看看官方的文檔註釋吧:web
/// A [Stack] of entries that can be managed independently.
///
/// Overlays let independent child widgets "float" visual elements on top of
/// other widgets by inserting them into the overlay's [Stack]. The overlay lets
/// each of these widgets manage their participation in the overlay using
/// [OverlayEntry] objects.
複製代碼
簡單總結一下,Overlay
是一個能夠管理的堆棧。咱們能夠經過將一個Widget
插入這個堆棧中,這樣就可讓此Widget
浮在其餘的Widget
之上,從而實現懸浮窗效果。咱們能夠經過OverlayEntry
對象的配置來管理Overlay
的層級關係。markdown
經過追蹤構造方法,咱們能夠發現它被Navigator
所建立,而Navigator
又被WidgetsApp
所建立。一般咱們runApp
啓動的page裏會建立的CupertinoApp
或者MaterialApp
都會建立WidgetsApp
,所以咱們在項目中能夠直接使用這個建立好的Overlay
。app
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback(
(timeStamp) {
OverlayEntry entry = OverlayEntry(
builder: (_) {
return IgnorePointer(
child: Center(
child: Text('OverlayEntry'),
),
);
},
);
Overlay.of(context).insert(entry);
},
);
}
複製代碼
Overlay
的使用很簡單,經過Overlay.of(context)
方法向上查找到OverlayState
,而後經過insert
或者insertAll
方法插入一個或多個懸浮Widget
。若是想要移除此懸浮Widget
,咱們能夠經過OverlayEntry
的remove()
方法實現。ide
爲何Overlay
插入的OverlayEntry
會在頁面上面懸浮呢?先來看看insert
方法作了什麼:佈局
void insert(OverlayEntry entry, { OverlayEntry below, OverlayEntry above }) {
...
entry._overlay = this;
setState(() {
_entries.insert(_insertionIndex(below, above), entry);
});
}
複製代碼
能夠看到OverlayEntry
被插入了_entries
中的指定位置,再看_insertionIndex
的實現:post
int _insertionIndex(OverlayEntry below, OverlayEntry above) {
assert(above == null || below == null);
if (below != null)
return _entries.indexOf(below);
if (above != null)
return _entries.indexOf(above) + 1;
return _entries.length;
}
複製代碼
咱們沒有指定可選參數的時候,默認會將OverlayEntry
插入到_entries
的末端。再看看OverlayState
的build
方法:ui
@override
Widget build(BuildContext context) {
// This list is filled backwards and then reversed below before
// it is added to the tree.
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,
));
}
}
return _Theatre(
skipCount: children.length - onstageCount,
children: children.reversed.toList(growable: false),
);
}
複製代碼
build
中會倒序遍歷_entries
,而後生成一個個_OverlayEntryWidget
,最後這個列表又會被倒序放入_Theatre
這個控件中。那這個_Theatre
是什麼呢?this
/// Special version of a [Stack], that doesn't layout and render the first
/// [skipCount] children.
///
/// The first [skipCount] children are considered "offstage".
class _Theatre extends MultiChildRenderObjectWidget 複製代碼
仍是得看源碼,能夠看出_Theatre
繼承本身MultiChildRenderObjectWidget
,這就好辦了,結合我以前寫的深刻研究Flutter佈局原理,我只用關心paint
方法就能夠知道這些children是如何繪製的了,來看paint
方法:url
@protected
void paintStack(PaintingContext context, Offset offset) {
RenderBox child = _firstOnstageChild;
while (child != null) {
final StackParentData childParentData = child.parentData as StackParentData;
context.paintChild(child, childParentData.offset + offset);
child = childParentData.nextSibling;
}
}
@override
void paint(PaintingContext context, Offset offset) {
if (_hasVisualOverflow) {
context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintStack);
} else {
paintStack(context, offset);
}
}
複製代碼
paint
很簡單,他會從第一個child開始循環繪製,也就是說children
會被一層一層的繪製在屏幕上,那麼咱們最後插入的OverlayEntry
天然是在最上了。spa
可能到這裏仍是會有疑惑,插入的OverlayEntry
只是在衆多的OverlayEntry
當中的最上,爲啥會在各個頁面之上呢?其實到這裏也能夠大概纔出緣由了:
/// Although you can create an [Overlay] directly, it's most common to use the
/// overlay created by the [Navigator] in a [WidgetsApp] or a [MaterialApp]. The
/// navigator uses its overlay to manage the visual appearance of its routes.
複製代碼
Overlay
的註釋中還有這麼一段:The navigator uses its overlay to manage the visual appearance of its routes.
,因此,咱們的各個頁面其實也是一個個OverlayEntry
,Navigator
的源碼仍是比較繞的,追蹤源碼看看:
@optionalTypeArgs
Future<T> push<T extends Object>(Route<T> route) {
...
_history.add(_RouteEntry(route, initialState: _RouteLifecycle.push));
_flushHistoryUpdates();
...
_afterNavigation(route);
return route.popped;
}
複製代碼
當咱們執行Navigator
的push
方法後,會先放_history中增長一條路由記錄,而後執行 _flushHistoryUpdates
方法:
void _flushHistoryUpdates({bool rearrangeOverlay = true}) {
int index = _history.length - 1;
...
final List<_RouteEntry> toBeDisposed = <_RouteEntry>[];
while (index >= 0) {
switch (entry.currentState) {
...
case _RouteLifecycle.push:
case _RouteLifecycle.pushReplace:
case _RouteLifecycle.replace:
assert(rearrangeOverlay);
entry.handlePush(
navigator: this,
previous: previous?.route,
previousPresent: _getRouteBefore(index - 1, _RouteEntry.isPresentPredicate)?.route,
isNewFirst: next == null,
);
assert(entry.currentState != _RouteLifecycle.push);
assert(entry.currentState != _RouteLifecycle.pushReplace);
assert(entry.currentState != _RouteLifecycle.replace);
if (entry.currentState == _RouteLifecycle.idle) {
continue;
}
break;
...
case _RouteLifecycle.disposed:
case _RouteLifecycle.staging:
assert(false);
break;
}
index -= 1;
next = entry;
entry = previous;
previous = index > 0 ? _history[index - 1] : null;
}
...
if (rearrangeOverlay)
overlay?.rearrange(_allRouteOverlayEntries);
}
複製代碼
_flushHistoryUpdates
方法很長,這裏看要點,push
方法執行後會加入的_RouteEntry
,會執行handlePush
方法,handlePush
方法會執行Route
的install
方法,看看咱們經常使用的MaterialPageRoute
的繼承關係:
藏得真的深,咱們能夠發現MaterialPageRoute
有一個父類OverlayRoute
,看看OverlayRoute
中的install
方法:
@override
void install() {
assert(_overlayEntries.isEmpty);
_overlayEntries.addAll(createOverlayEntries());
super.install();
}
複製代碼
install
方法會執行createOverlayEntries
方法生成Iterable<OverlayEntry>
並加入列表。到這裏咱們也大概能夠搞清楚了,push
一個頁面本質上仍是push
了一個OverlayEntry
。_flushHistoryUpdates
方法最終會調用OverlayState
的rearrange
方法處理列表:
void rearrange(Iterable<OverlayEntry> newEntries, { OverlayEntry below, OverlayEntry above }) {
final List<OverlayEntry> newEntriesList = newEntries is List<OverlayEntry> ? newEntries : newEntries.toList(growable: false);
...
if (newEntriesList.isEmpty)
return;
if (listEquals(_entries, newEntriesList))
return;
final LinkedHashSet<OverlayEntry> old = LinkedHashSet<OverlayEntry>.from(_entries);
for (final OverlayEntry entry in newEntriesList) {
entry._overlay ??= this;
}
setState(() {
_entries.clear();
_entries.addAll(newEntriesList);
old.removeAll(newEntriesList);
_entries.insertAll(_insertionIndex(below, above), old);
});
}
複製代碼
rearrange
方法會先複製一遍舊的_entries
列表,而後清空_entries
列表,加入最新的路由列表數據,再從舊列表中去重路由列表,這樣剩下的基本都是用戶本身insert的OverlayEntry
,最後再將舊列表的數據插入新的列表,這樣用戶的OverlayEntry
仍是會在各個頁面的上層。