介绍
现在的通知权限不好搞了,所以一些应用增加了内部通知,即:类似通知的显示方式,但是只能在应用(前台状态下)内显示,其本质是一个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('通知弹出完毕'));
}),
点击按钮后的样式:
至此整个功能就实现了,谢谢大家阅读,如果有错误或者更好的方法,欢迎回复交流。