系列指路:
Flutter自繪組件:微信懸浮窗(一)
Flutter自繪組件:微信懸浮窗(二)
Flutter自繪組件:微信懸浮窗(三)
Flutter自繪組件:微信懸浮窗(四)
懸浮窗最終的實(shí)現(xiàn)效果如下:
實(shí)現(xiàn)思路
我們?cè)谏弦黄恼轮幸呀?jīng)完整實(shí)現(xiàn)了懸浮窗的完整代碼,現(xiàn)在需要的只是將FloatingWindow
類套上一層OverlayEntry
的浮層就可以完結(jié)撒花了春锋。我們先來(lái)講講OverlayEntry
是什么。
OverlayEntry
OverlayEntry
可以理解為一個(gè)浮層元素闷旧,我們常見的MaterialApp
創(chuàng)建時(shí),會(huì)創(chuàng)建一個(gè)Overlay
浮層集合茂卦,然后利用這個(gè) Navigator
來(lái)管理路由中的界面冤留。
由于百度了一下,沒有百度到詳解帚桩,對(duì)于這種情況,我們也只能從頭自己進(jìn)行詳解嘹黔,即讀源碼注釋账嚎。
我們先來(lái)讀一下Overlay
的源碼注釋:
/// 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.
///
/// 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.
///
/// See also:
///
/// * [OverlayEntry].
/// * [OverlayState].
/// * [WidgetsApp].
/// * [MaterialApp].
class Overlay extends StatefulWidget
翻譯過(guò)來(lái)的大致意思使,Overlay
可以通過(guò)把獨(dú)立的子Widget
們插入到Overlay
的Stack
中來(lái)讓他們“漂浮”在頂層Widget
們其他可見元素之上儡蔓,而且這些獨(dú)立的子Widget
們通過(guò)OverlayEntry
來(lái)管理自身是否參與到Overlay
中郭蕉。
我們?cè)谔岬?code>MaterialApp已經(jīng)存在了一個(gè)Overlay
,我們只需要通過(guò)Overlay.of
即可獲得當(dāng)前MaterialApp
的OverlayState
對(duì)象喂江。我們可以看一下這個(gè)方法的源碼:
/// The state from the closest instance of this class that encloses the given context.
///
/// In debug mode, if the `debugRequiredFor` argument is provided then this
/// function will assert that an overlay was found and will throw an exception
/// if not. The exception attempts to explain that the calling [Widget] (the
/// one given by the `debugRequiredFor` argument) needs an [Overlay] to be
/// present to function.
///
/// Typical usage is as follows:
///
/// ```dart
/// OverlayState overlay = Overlay.of(context);
/// ```
///
/// If `rootOverlay` is set to true, the state from the furthest instance of
/// this class is given instead. Useful for installing overlay entries
/// above all subsequent instances of [Overlay].
static OverlayState of(
BuildContext context, {
bool rootOverlay = false,
Widget debugRequiredFor,
})
可以看到rooteOverlay
會(huì)獲取到最遙遠(yuǎn)的Overlay
實(shí)例的狀態(tài)召锈,如果我們需要把OverlayEntry
置于后來(lái)所有的overlay
之上的話是十分有用的。我們的懸浮窗即使在切換頁(yè)面获询,無(wú)論何種情況下都應(yīng)置于最高層涨岁,因此,這個(gè)參數(shù)應(yīng)該設(shè)為true
吉嚣。
可能需要注意的邏輯就在于梢薪,當(dāng)懸浮窗的列表項(xiàng)全部關(guān)閉時(shí),再進(jìn)行添加則需要移除原先的浮層尝哆,然后再重新申請(qǐng)浮層資源沮尿。如果當(dāng)前浮層還存在則不需要這么做,我們?nèi)绾蝸?lái)判斷懸浮窗的列表項(xiàng)是否已經(jīng)全部關(guān)閉呢较解?我們?cè)趯?shí)現(xiàn)FloatingWindow
的時(shí)候定義了一個(gè)isEmpty
變量來(lái)判斷列表是否為空,我們只需要把共享數(shù)據(jù)windowModel
設(shè)為靜態(tài)數(shù)據(jù)赴邻,然后再在FloatingWidget
中定義一個(gè)isEmpty
的方法就可以得知當(dāng)前的懸浮窗列表項(xiàng)是否為空印衔。而往懸浮窗中添加列表項(xiàng)的實(shí)現(xiàn)也很簡(jiǎn)單,只需要把FloatingWindowState
中代表列表項(xiàng)的數(shù)據(jù)列表ls
設(shè)為靜態(tài)數(shù)據(jù)姥敛,再在FloatingWidget
中添加靜態(tài)方法add
用于向ls
中添加列表項(xiàng)數(shù)據(jù)奸焙,FloatingWindow
修改后的代碼如下:
/// [FloatingWindow] 懸浮窗
class FloatingWindow extends StatefulWidget {
@override
_FloatingWindowState createState() => _FloatingWindowState();
/// 添加列表項(xiàng)數(shù)據(jù)
static void add(Map<String,String> element){
_FloatingWindowState.ls.add(element);
}
/// 判斷列表項(xiàng)是否為空
static bool isEmpty(){
return _FloatingWindowState.windowModel.isEmpty;
}
}
class _FloatingWindowState extends State<FloatingWindow> {
static List<Map<String,String>> ls = [];
/// 懸浮窗共享數(shù)據(jù)
static FloatingWindowModel windowModel;
/// [isEntering] 列表項(xiàng)是否擁有進(jìn)場(chǎng)動(dòng)畫
bool isEntering = true;
@override
void initState() {
// TODO: implement initState
super.initState();
windowModel = new FloatingWindowModel(dataList: ls,isLeft: true);
isEntering = true;
}
@override
Widget build(BuildContext context) {
return FloatingWindowSharedDataWidget(
data: windowModel,
child: windowModel.isEmpty ? Container() : Stack(
fit: StackFit.expand,
children: [
/// 列表項(xiàng)遮蓋層,增加淡化切換動(dòng)畫
AnimatedSwitcher(
duration: Duration(milliseconds: 100),
child: windowModel.isButton ? Container() : GestureDetector(
onTap: (){
FloatingItem.reverse();
Future.delayed(Duration(milliseconds: 110),(){
setState(() {
windowModel.isButton = true;
windowModel.itemTop = -1.0;
});
});
},
child: Container(
decoration: BoxDecoration(color: Color.fromRGBO(0xEF, 0xEF, 0xEF, 0.9)),
),
),
),
NotificationListener<ClickNotification>(
onNotification: (notification){
/// 列表項(xiàng)關(guān)閉事件
if(notification.deletedIndex != -1){
windowModel.deleteIndex = notification.deletedIndex;
setState(() {
FloatingItem.resetList();
windowModel.dataList.removeAt(notification.deletedIndex);
isEntering = false;
});
}
/// 列表點(diǎn)擊事件
if(notification.clickIndex != -1){
print(notification.clickIndex);
}
/// 懸浮按鈕點(diǎn)擊Widget改變事件
if(notification.changeWidget){
setState(() {
/// 釋放列表進(jìn)出場(chǎng)動(dòng)畫資源
FloatingItem.resetList();
windowModel.isButton = false;
isEntering = true;
});
}
return false;
},
child: windowModel.isButton ? FloatingButton():FloatingItems(isEntering: isEntering,),
)
],
),
);
}
}
FloatingWindowOverEntry
代碼實(shí)現(xiàn)如下:
import 'package:flutter/material.dart';
import 'FloatingWindow.dart';
/// [FloatingWindowOverlayEntry] 懸浮窗浮層顯示
class FloatingWindowOverlayEntry{
/// [_overlayEntry] 懸浮窗浮層
static OverlayEntry _overlayEntry;
/// [_overlayEntry] 懸浮窗
static FloatingWindow _floatingWindow;
/// 添加條項(xiàng)
static void add(BuildContext context,{Map<String,String> element}){
/// 如果沒有浮層則初始化
if(_overlayEntry == null){
_floatingWindow = new FloatingWindow();
_overlayEntry = OverlayEntry(
builder: (BuildContext context) {
return _floatingWindow;
}
);
Overlay.of(context,rootOverlay: true).insert(_overlayEntry);
}
/// 存在浮層
else{
/// 如果列表項(xiàng)為空,則清除原先浮層与帆,然后新建浮層插入了赌。
if(FloatingWindow.isEmpty()){
/// 清除原先浮層
_overlayEntry.remove();
_floatingWindow = new FloatingWindow();
/// 新建浮層
_overlayEntry = OverlayEntry(
builder: (BuildContext context){
return _floatingWindow;
}
);
/// 插入浮層
Overlay.of(context,rootOverlay: true).insert(_overlayEntry);
}
}
/// 添加列表項(xiàng)數(shù)據(jù)
FloatingWindow.add(element);
/// 標(biāo)記臟值刷新
_overlayEntry.markNeedsBuild();
}
}
使用方法也十分簡(jiǎn)單:
FloatingWindowOverlayEntry.add(context,element: {'title': "微信懸浮窗","imageUrl":"assets/Images/vnote.png"}),
main.dart調(diào)用代碼
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home:new Scaffold(
body: Stack(
children: [
/// 用于測(cè)試遮蓋層是否生效
Positioned(
left: 250,
top: 250,
child: Container(width: 50,height: 100,color: Colors.red,),
),
Positioned(
left: 250,
top: 50,
child:TestWidget()
),
],
),
),
);
}
}
class TestWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
RaisedButton(
child: Text('show'),
),
RaisedButton(
onPressed: () => FloatingWindowOverlayEntry.add(context,element: {'title': "微信懸浮窗","imageUrl":"assets/Images/vnote.png"}),
child: Text('add'),
)
],
);
}
}
總結(jié)
完結(jié)撒花,可以安心寫其他系列玄糟,遲點(diǎn)可能會(huì)把這個(gè)項(xiàng)目開源到gitee
上勿她。接下來(lái)更新drogon
系列/uni-app
系列以及其他項(xiàng)目實(shí)戰(zhàn)記錄。