隨著業務的擴充套件和延伸,需要的功能也是多種多樣,而同一種效果可以有多種實現方案;小菜今天學習一下通過 Overlay 實現基本的懸浮引導效果;
Overlay 以浮層的方式管理單獨的 item 儲存在棧中(後進先出);Overlay 其原始碼也是採用的 Stack 浮層,將 OverEntry 逐個加入到 Overlay 中進行展示,OverEntry 可以使用 Positioned 或 AnimatedPositioned 在 Overlay 中定義自身的位置;
當建立 MaterialApp 時,它會自動建立一個 Navigator,之後建立一個 Overlay,然後利用這個 Navigator 來管理路由中的介面;
原始碼分析
const Overlay({
Key key,
this.initialEntries = const <OverlayEntry>[],
})
class OverlayEntry
OverlayEntry({
@required this.builder,
bool opaque = false,
bool maintainState = false,
})
}
複製程式碼
分析原始碼可知,Overlay 主要是由 OverlayEntry 浮層元素組成的,並以棧的方式儲存;opaque 為當前浮層元素是否遮蓋整個 Overlay 浮層;maintainState 一般與 opaque 共同使用,是否將不透明的浮層元素新增到 Widget Tree 中;
案例嘗試
Overlay 作為浮層的應用效果很廣泛,網上很多老師都通過 Overlay 實現自定義 Toast / Dialog / PopupMenu / List item 等,但小菜嘗試通過 Overlay 實現升級過程中的新手引導; Overlay 主要是通過 insert / insertAll 方式加入 OverEntry 浮層元素,通過 remove 移除浮層元素;
insert One OverEnrty
如果僅需展示一個 OverEntry 浮層元素,可以通過 insert 加入到 Overlay 中,也可以通過 insertAll 加入僅有一個 OverEntry 的陣列;最終通過 remove 關閉浮層元素,注意陣列中的元素要全部 remove;
// insert
overlayEntry = OverlayEntry(builder: (context) {
return Stack(children: <Widget>[Positioned(
top: (height - 200) * 0.5, left: (width - 200) * 0.5,
child: GestureDetector(
onTap: () => overlayEntry.remove(),
child: _itemContainer(Colors.blue.withOpacity(0.6))))
]);
});
Overlay.of(context).insert(overlayEntry);
複製程式碼
// insertAll
overlayEntry = OverlayEntry(builder: (context) {
return Stack(children: <Widget>[Positioned(
top: (height - 200) * 0.5, left: (width - 200) * 0.5,
child: GestureDetector(
onTap: () { overlayEntry.remove(); overlayEntryList.clear(); },
child: _itemContainer(Colors.brown.withOpacity(0.6))))
]);
});
overlayEntryList.add(overlayEntry);
Overlay.of(context).insertAll(overlayEntryList);
複製程式碼
insert Three OverEntrys
如果需要展示多個 OverEntry 浮層元素時,只能用 insertAll 新增到 Overlay 中,其中預設是以棧方式加入的;其中 insertAll 會一次性的把所有 OverEntry 均加入到 Overlay 中;
overlayEntryList.add(OverlayEntry(builder: (context) {
return Stack(children: <Widget>[
Positioned(top: (height - 200) * 0.5 - 50, left: (width - 200) * 0.5 - 50,
child: GestureDetector(
onTap: () {
--overIndex;
overlayEntryList[overIndex].remove();
overlayEntryList.removeLast();
},
child: _itemContainer(Colors.red.withOpacity(0.6))))
]);
}));
overlayEntryList.add(OverlayEntry(builder: (context) {
return Stack(children: <Widget>[
Positioned(top: (height - 200) * 0.5, left: (width - 200) * 0.5,
child: GestureDetector(
onTap: () {
--overIndex;
overlayEntryList[overIndex].remove();
overlayEntryList.removeLast();
},
child: _itemContainer(Colors.orange.withOpacity(0.6))))
]);
}));
overlayEntryList.add(OverlayEntry(builder: (context) {
return Stack(children: <Widget>[
Positioned(top: (height - 200) * 0.5 + 50, left: (width - 200) * 0.5 + 50,
child: GestureDetector(
onTap: () {
--overIndex;
overlayEntryList[overIndex].remove();
overlayEntryList.removeLast();
},
child: _itemContainer(Colors.blue.withOpacity(0.6))))
]);
}));
overIndex = overlayEntryList.length;
Overlay.of(context).insertAll(overlayEntryList);
複製程式碼
若需要逐次展示多個 OverlayEntry 可以在點選事件中單獨加入新的 OverlayEntry;
overlayEntry = OverlayEntry(builder: (context) {
return Stack(children: <Widget>[
Positioned(top: (height - 200) * 0.5 - 50, left: (width - 200) * 0.5 - 50,
child: GestureDetector(
onTap: () {
overlayEntry.remove();
Overlay.of(this.context).insert(overlayEntry2);
},
child: _itemContainer(Colors.red.withOpacity(0.6))))
]);
});
overlayEntry2 = OverlayEntry(builder: (context) {
return Stack(children: <Widget>[
Positioned(top: (height - 200) * 0.5, left: (width - 200) * 0.5,
child: GestureDetector(
onTap: () {
overlayEntry2.remove();
Overlay.of(this.context).insert(overlayEntry3);
},
child: _itemContainer(Colors.orange.withOpacity(0.6))))
]);
});
overlayEntry3 = OverlayEntry(builder: (context) {
return Stack(children: <Widget>[
Positioned(top: (height - 200) * 0.5 + 50, left: (width - 200) * 0.5 + 50,
child: GestureDetector(onTap: () => overlayEntry3.remove(), child: _itemContainer(Colors.blue.withOpacity(0.6))))
]);
});
Overlay.of(context).insert(overlayEntry);
複製程式碼
注意事項
1. Overlay 為全域性覆蓋,並非當前 Page,需要重新定義返回按鍵等;若沒有 remove 則返回上一個頁面依然展示浮層元素;若 remove 其他未加入浮層的元素會返回失敗;
return WillPopScope(
onWillPop: () async {
if (overListIndex == 6) {
for (int i = overlayEntryList.length; i > 0; i--) {
overlayEntryList[i - 1].remove();
}
overlayEntryList.clear();
overIndex = 0;
} else if (overListIndex == 7) {
overlayEntry.remove();
} else if (overListIndex == 8) {
overlayEntry2.remove();
} else if (overListIndex == 9) {
overlayEntry3.remove();
}
if (overIndex == 4) {
overlayEntry.remove();
overlayEntry0.remove();
} else if (overIndex == 3) {
overlayEntry2.remove();
overlayEntry0.remove();
} else if (overIndex == 2) {
overlayEntry3.remove();
overlayEntry0.remove();
} else if (overIndex == 5) {
overlayEntry.remove();
}
overIndex = 0;
return true;
},
child: Container(...)
);
複製程式碼
2. 使用 insertAll 新增浮層元素時,不要同時加入多次同一個 OverlayEntry;
overlayEntry = OverlayEntry(builder: (context) {
return Stack(children: <Widget>[
Positioned(top: (height - 200) * 0.5 - 50, left: (width - 200) * 0.5 - 50,
child: GestureDetector(
onTap: () => overlayEntry.remove(),
child: _itemContainer(Colors.red.withOpacity(0.6))))
]);
});
// 錯誤寫法,加入多次同一個 OverlayEntry
overlayEntryList.add(overlayEntry);
overlayEntryList.add(overlayEntry);
overlayEntryList.add(overlayEntry);
Overlay.of(this.context).insertAll(overlayEntryList);
複製程式碼
3. opaque = true 時會完全覆蓋之前的浮層元素,為不透明的,且不可透過當前浮層點選下一個浮層元素;maintainState 為在上層元素 opaque = true,即不透明的完全覆蓋下層元素時,被覆蓋的這個元素設定的 maintainState 是否提前構建;
overlayEntryList.add(OverlayEntry(builder: (context) {
return Stack(children: <Widget>[
Positioned( top: (height - 200) * 0.5 - 50, left: (width - 200) * 0.5 - 50,
child: GestureDetector(
onTap: () { --overIndex; overlayEntryList[overIndex].remove(); },
child: _itemContainer(Colors.red.withOpacity(0.6))))
]);
}));
overlayEntryList.add(OverlayEntry(
opaque: true, maintainState: true,
builder: (context) {
return Material(
color: Colors.amber.withOpacity(0.4),
child: Stack(children: <Widget>[
Positioned( top: (height - 200) * 0.5, left: (width - 200) * 0.5,
child: GestureDetector(
onTap: () { --overIndex; overlayEntryList[overIndex].remove(); },
child: _itemContainer(Colors.orange.withOpacity(0.6))))
]));
}));
overlayEntryList.add(OverlayEntry(
opaque: true, maintainState: false,
builder: (context) {
return Material(
color: Colors.lightBlueAccent.withOpacity(0.4),
child: Stack(children: <Widget>[
Positioned( top: (height - 200) * 0.5 + 50, left: (width - 200) * 0.5 + 50,
child: GestureDetector(
onTap: () { --overIndex; overlayEntryList[overIndex].remove(); },
child: _itemContainer(Colors.blue.withOpacity(0.6))))
]));
}));
overIndex = overlayEntryList.length;
Overlay.of(context).insertAll(overlayEntryList);
複製程式碼
小菜對 Overlay 的嘗試還比較基礎,使用場景也比較小,如有錯誤,請多多指導!
阿策小和尚