Flutter Notification——實現應用內部的自定義widget通知功能
技術標籤:flutterandroid前端androidiosjavascriptcssflutter
介紹
現在的通知許可權不好搞了,所以一些應用增加了內部通知,即:類似通知的顯示方式,但是隻能在應用(前臺狀態下)內顯示,其本質是一個view。
接下來我們就實現一個可以將我們自定義的widget以通知的形式顯示出來的功能。
此功能已加入Bedrock 開發框架
支援單個/批量顯示通知
Flutter Bedrock 快速開發框架 Mvvm+Provider
樣式如下:
實現
INotification
首先,我們先定義一下通知相關的控制行為INotification
abstract class INotification{ //顯示一個通知 Future showNotificationFromTop({@required Widget child,Duration animationDuration, Duration notifyDwellTime}); //顯示一批通知 Future showNotifyListFromTop({@required List<Widget> children,Duration animationDuration, Duration notifyDwellTime}); //預留方法 Future showNotificationCustom({@required Widget child,Duration animationDuration, Duration notifyDwellTime}); //增加/移除監聽器等,可以監聽一個通知的執行狀態( 執行中,執行完畢) void addNotifyListener(NotifyStatusListener listener); void removeNotifyListener(NotifyStatusListener listener); void clearAllListener(); }
行為定義好了,我們開始著手實現它
NotificationHandler 顯示單個通知
我們通過NotificationHandler 實現上面定義的方法。
先看showNotificationFromTop 這個方法:
顯示一個通知
/// @param [animationDuration] child 從頂部滑出/收回所需時間 /// @param [notifyDwellTime] 通知 停留時間 @override Future showNotificationFromTop({@required Widget child,Duration animationDuration, Duration notifyDwellTime}) async{ ///通過 .then等,也可以監聽動畫狀態. Completer completer = Completer(); ///自定義類,增加overlay 和 child的聯絡 NotifyOverlayEntry notifyOverlayEntry = NotifyOverlayEntry(child, animationDuration??Duration(milliseconds: 500), notifyDwellTime??Duration(seconds: 2000),callback: (){ completer.complete(); //這裡後面會講到 if(!streamDone){ _subscription.resume(); } ///通知結束回撥 _notifyListener(NotifyStatus.Completed); }); /// assume [.insert] is start notify in [NotifyStatus.Running]. _notifyListener(NotifyStatus.Running); ///通過overlay 來新增通知 Overlay.of(context).insert(notifyOverlayEntry.overlayEntry); return completer.future; }
為了增加 overlay和child(通知裡顯示的widget)的聯絡,這裡定義了一個NotifyOverlayEntry類。
NotifyOverlayEntry
class NotifyOverlayEntry{ final Widget notifyWidget; final Duration animationDuration; final Duration notifyDwellTime; final VoidCallback callback; OverlayEntry entry; bool notifyDone = false; OverlayEntry get overlayEntry => entry; NotifyOverlayEntry(this.notifyWidget, this.animationDuration, this.notifyDwellTime,{@required this.callback, NotifyType notifyType = NotifyType.FromTop}){ ///根據型別 構建不同顯示方式的通知 ///目前只有一個從頂部滑出的方式 ///如果需要拓展,請務必遵從下面的設計方式 switch(notifyType){ case NotifyType.FromTop: entry = OverlayEntry(builder: (ctx){ return FromTopNotifyWidget(notifyWidget,(notifyDone){ this.notifyDone = notifyDone; if(notifyDone) overlayEntry.remove(); callback(); }, animationDuration,notifyDwellTime).generateWidget(); }); break; } } }
程式碼很簡單,根據通知顯示方式生成對應的overlayEntry, 具體的滑出動畫由FromTopNotifyWidget來負責。
FromTopNotifyWidget
其內部實現非常簡單,只是對我們的child做了一個位移的動畫。
從螢幕上(外部)滑入螢幕內
程式碼如下:
class FromTopNotifyWidget extends WidgetState with SingleTickerProviderStateMixin {
final Widget child;
final Duration animationDuration;
final Duration notifyDwellTime;
///動畫彈出並收回後,會執行這個回撥
final NotifyDone notifyDone;
AnimationController controller;
Animation animation;
FromTopNotifyWidget(this.child,this.notifyDone, this.animationDuration, this.notifyDwellTime);
@override
void initState() {
// TODO: implement initState
super.initState();
controller = AnimationController(vsync: this,duration: animationDuration);
animation = Tween<Offset>(begin: Offset(0,-1),end: Offset.zero).animate(controller);
controller.addStatusListener((status) {
if(status == AnimationStatus.completed){
Future.delayed(notifyDwellTime)
.whenComplete(() => controller?.reverse()?.whenComplete(() => notifyDone(true)));
}
});
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
controller.forward();
});
}
@override
void dispose() {
controller?.dispose();
// TODO: implement dispose
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [AnimatedBuilder(
animation: animation,
builder: (ctx,c){
return SlideTransition(
position:animation ,
child: child,);
},
)],
);
}
}
至此,顯示單個通知的功能就實現了,使用方法如下:
buildBtn('彈出通知', (){
NotificationHandler(context)
..showNotificationFromTop(notifyDwellTime: Duration(seconds: 2),
child: buildNotifyChild('notification'),);
}),
點選按鈕後,樣式如下:
當伺服器有多個通知需要順序顯示時,該如何實現呢? 我們繼續向下看
NotificationHandler 批量顯示通知
這裡我們需要用到stream,先初始化一波:
///通知是否顯示完畢
bool streamDone = true;
StreamController<NotifyListItemWrapper> _streamController;
StreamSink<NotifyListItemWrapper> _sink ;
StreamSubscription<NotifyListItemWrapper> _subscription;
NotificationHandler._(this.context){
///在建構函式內進行初始化
_streamController = StreamController<NotifyListItemWrapper>();
_sink = _streamController.sink;
///進行監聽
_subscription = _streamController.stream.listen((event) {
if(event == null){
///當事件為空時,說明批量通知顯示完畢
streamDone = true;
if(!_subscription.isPaused){
_subscription.pause();
}
if(listCompleter != null){
listCompleter.complete();
listCompleter = null;
}
return ;
}
///確保通知挨個進行
_subscription.pause();
///這裡使用 【顯示單個通知】的方法
showNotificationFromTop(child: event.child,animationDuration: event.animationDuration,notifyDwellTime: event.notifyDwellTime);
});
_streamController.done.then((v) {
streamDone = true;
});
_subscription.pause();
}
為了方便事件資料的傳遞,這裡額外增加了一個NotifyListItemWrapper類,程式碼如下:
class NotifyListItemWrapper{
final Widget child;
final Duration animationDuration;
final Duration notifyDwellTime;
NotifyListItemWrapper(this.child, this.animationDuration, this.notifyDwellTime);
}
主要是對child和動畫事件做一個包裹。
接下來看一下,批量顯示通知【showNotifyListFromTop】的方法如何實現的:
Completer listCompleter;
@override
Future showNotifyListFromTop({List<Widget> children, Duration animationDuration, Duration notifyDwellTime})async {
listCompleter = Completer();
streamDone = false;
///先將stream恢復
_subscription.resume();
children.forEach((element) {
///批量新增資料
_sink.add(NotifyListItemWrapper(element,animationDuration,notifyDwellTime));
});
///傳送一個空資料,以通知事件傳送完畢
_sink.add(null);
return listCompleter.future;
}
很簡單,至此我們就實現了批量通知顯示的方法,下面看一下如何使用:
buildBtn('彈出多個通知', (){
NotificationHandler(context)
..showNotifyListFromTop(notifyDwellTime: Duration(seconds: 2),
children:List<Widget>.generate(3, (index) => buildNotifyChild('notification $index')),)
.whenComplete(() => debugPrint('通知彈出完畢'));
}),
點選按鈕後的樣式:
至此整個功能就實現了,謝謝大家閱讀,如果有錯誤或者更好的方法,歡迎回復交流。