如何在Flutter的任何屏幕/頁(yè)面上顯示內(nèi)容窃判?
難度:中級(jí)
前言
最近我寫(xiě)了一些代碼來(lái)處理WebSockets,我需要在任何屏幕/頁(yè)面的頂部顯示一個(gè)圖標(biāo)喇闸,以便在服務(wù)器發(fā)送通知時(shí)通知用戶袄琳。
我嘗試使用PopupRoute,showDialog ...但是永遠(yuǎn)無(wú)法獲得我想要實(shí)現(xiàn)的目標(biāo)燃乍。
當(dāng)我們使用路由時(shí)唆樊,整個(gè)屏幕被覆蓋,并且用戶無(wú)法繼續(xù)使用當(dāng)前頁(yè)面“ 正常 ” 工作刻蟹,因?yàn)楹笳弑涣硪粋€(gè)頁(yè)面替換(覆蓋)逗旁。因此,我繼續(xù)我的調(diào)查舆瘪,發(fā)現(xiàn)Overlay和OverlayEntry的概念片效。
通過(guò)閱讀Flutter的源代碼,我發(fā)現(xiàn)Flutter使用OverlayEntry來(lái)顯示drag avatar(請(qǐng)參閱Draggable)英古。因此淀衣,我明白這就是我要找的東西。
覆蓋
Flutter文檔說(shuō):“ * overlay是一組可以獨(dú)立管理的條目召调。Overlays讓獨(dú)立的widgets“浮動(dòng)”在其他widgets之上......* ”膨桥。
用我自己很簡(jiǎn)單的話說(shuō)。
overlay不外乎是一個(gè)layer (StatefulWidget)唠叛,在所有widget之上只嚣,它包含一個(gè)Stack,這里我們可以添加任何小部件艺沼。
這些小部件稱為OverlayEntry
OverlayState
Overlay是一個(gè)StatefulWidget册舞。OverlayState是Overlay實(shí)例的State,負(fù)責(zé)渲染澳厢。
OverlayEntry
OverlayEntry是一個(gè)Widget环础,我們可以把他插入到Overlay。OverlayEntry一次最多只能插入到一個(gè)Overlay剩拢。
由于Overlay使用Stack布局线得,因此overlay窗口可以使用Positioned和AnimatedPositioned將自己定位在疊加層中。
嗯徐伐,這正是我需要的:能夠在屏幕上的任何地方顯示我的通知圖標(biāo)贯钩。
讓我們直接跳轉(zhuǎn)到一些代碼
以下類在坐標(biāo)(50.0,50.0)處顯示一個(gè)Icon,并在2秒后將其刪除办素。
import 'package:flutter/material.dart';
import 'dart:async';
class ShowNotificationIcon {
void show(BuildContext context) async {
OverlayState overlayState = Overlay.of(context);
OverlayEntry overlayEntry = new OverlayEntry(builder: _build);
overlayState.insert(overlayEntry);
await new Future.delayed(const Duration(seconds: 2));
overlayEntry.remove();
}
Widget _build(BuildContext context){
return new Positioned(
top: 50.0,
left: 50.0,
child: new Material(
color: Colors.transparent,
child: new Icon(Icons.warning, color: Colors.purple),
),
);
}
}
如您所見(jiàn)角雷,為了能夠顯示圖標(biāo),我們需要提供Context性穿。為什么勺三?
官方文檔沒(méi)有解釋這一點(diǎn),但是需曾,看一下源代碼吗坚,我們看到為每個(gè)Route創(chuàng)建了一個(gè)Overlay,因此它屬于一個(gè)Context樹(shù)(參見(jiàn)我關(guān)于上下文概念的文章)呆万。因此商源,有必要找到對(duì)應(yīng)于特定Context的OverlayState(第7行)。
就是這個(gè)谋减! 從這個(gè)例子中牡彻,我們可以推導(dǎo)出任何類型的疊加內(nèi)容和行為。
為了說(shuō)明這一點(diǎn)出爹,讓我們構(gòu)建一種閃爍的Toast庄吼,它在屏幕上的某個(gè)位置顯示一個(gè)Widget,并在一定時(shí)間后消失以政。
示例:閃爍Toast
第一類是前一個(gè)例子的概括霸褒。它允許提供外部Widget構(gòu)造函數(shù)。
import 'dart:async';
import 'package:flutter/material.dart';
class BlinkingToast {
bool _isVisible = false;
///
/// BuildContext context: the context from which we need to retrieve the Overlay
/// WidgetBuilder externalBuilder: (compulsory) external routine that builds the Widget to be displayed
/// Duration duration: (optional) duration after which the Widget will be removed
/// Offset position: (optional) position where you want to show the Widget
///
void show({
@required BuildContext context,
@required WidgetBuilder externalBuilder,
Duration duration = const Duration(seconds: 2),
Offset position = Offset.zero,
}) async {
// Prevent from showing multiple Widgets at the same time
if (_isVisible){
return;
}
_isVisible = true;
OverlayState overlayState = Overlay.of(context);
OverlayEntry overlayEntry = new OverlayEntry(
builder: (BuildContext context) => new BlinkingToastWidget(
widget: externalBuilder(context),
position: position,
),
);
overlayState.insert(overlayEntry);
await new Future.delayed(duration);
overlayEntry.remove();
_isVisible = false;
}
}
第二個(gè)類在屏幕上的某個(gè)位置顯示W(wǎng)idget并使其閃爍盈蛮。
有關(guān)動(dòng)畫(huà)概念的進(jìn)一步說(shuō)明废菱,請(qǐng)參閱我關(guān)于此主題的文章。
class BlinkingToastWidget extends StatefulWidget {
BlinkingToastWidget({
Key key,
@required this.widget,
@required this.position,
}): super(key: key);
final Widget widget;
final Offset position;
@override
_BlinkingToastWidgetState createState() => new _BlinkingToastWidgetState();
}
class _BlinkingToastWidgetState extends State<BlinkingToastWidget>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = new AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
_animation = new Tween(begin: 0.0, end: 1.0).animate(new CurvedAnimation(
parent: _controller,
curve: new Interval(0.0, 0.5)
))
..addListener(() {
if (mounted){
setState(() {
// Refresh
});
}
})
..addStatusListener((AnimationStatus status){
if (status == AnimationStatus.completed){
_controller.reverse().orCancel;
} else if (status == AnimationStatus.dismissed){
_controller.forward().orCancel;
}
});
_controller.forward().orCancel;
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return new Positioned(
top: widget.position.dy,
left: widget.position.dx,
child: new IgnorePointer(
child: new Material(
color: Colors.transparent,
child: new Opacity(
opacity: _animation.value,
child: widget.widget,
),
),
));
}
}
要調(diào)用此BlinkingToast:
BlinkingToast toast = new BlinkingToast();
toast.show(
context: context,
externalBuilder: (BuildContext context){
return new Icon(Icons.warning, color: Colors.purple);
},
duration: new Duration(seconds: 5),
position: new Offset(50.0, 50.0),
);
結(jié)論
這是一篇非常簡(jiǎn)短的文章抖誉,僅旨在分享在任何屏幕上顯示W(wǎng)idget的方式殊轴。