1. 程式人生 > 其它 >Flutter Notification——實現應用內部的自定義widget通知功能

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('通知彈出完畢'));
          }),

點選按鈕後的樣式:

在這裡插入圖片描述

至此整個功能就實現了,謝謝大家閱讀,如果有錯誤或者更好的方法,歡迎回復交流。

Demo地址

Flutter Bedrock 快速開發框架,內含Demo

系列文章

Flutter 仿網易雲音樂App(基礎版)

Flutter版 仿.知乎列表的視差效果

Flutter——實現網易雲音樂的漸進式卡片切換

Flutter 仿同花順自選股列表