Flutter 統一彈窗管理的思考與實現

Cheney2006發表於2020-04-18

背景

  在專案開發中經常會碰到有時要根據業務後臺介面顯示一些提示框或者引導框什麼的,有時這些彈框要多次顯示或只顯示一次,並且它們內部也有一定的優先順序,還要保證一個一個的顯示,不能重疊。還有一些操作完成後,進入到了指定的頁面才顯示這個操作之後的提示或引導彈窗。特別是在 Flutter 中,這些還顯示更為複雜,因為原生的彈窗的優先順序比 Flutter 的優先順序高,例如許可權申請、指紋等。

  這就使得處理更為複雜,通用的處理是在介面通些一些變數來控制當前是否有彈窗在顯示,來判斷其它的彈窗要不要顯示,或者通過標識來確認彈窗是否已經顯示過;這就使得介面上有各種各樣的臨時變更,及各種判斷,這樣就非常影響整個介面的可讀性。

  所以在這裡就把專案中一些處理的思路與方法跟大家分享下,大家一起學習。

現狀

  1. 彈窗樣式多樣、多個彈窗重疊、多個彈窗間有優先順序
  2. 有時彈窗需指定特定頁面
  3. 同一彈窗會重複顯示
  4. 彈窗無法指定關閉

思路

  • 對於上述場景,對於自動觸發彈出的彈窗,統一加到彈窗管理中。
  • 對於沒有指定頁面的彈窗,則加入到頂層頁面中。彈窗管理有特定頂層頁面,用於顯示當前頂層需顯示的彈窗列表
  • 對於需要在特定頁面顯示的彈窗,則在需要顯示前判斷當前頂層檢視是否為特定頁面(因為 Flutter 中彈窗無需與頁面關聯)。
  • 每個彈窗都有優先順序,在頁面需要顯示前,會先進行優先順序排序,再根據排序結果進行展示
  • 每個彈窗都會生成一個固定的唯一標識,用於處理彈窗重複問題
  • 對於彈窗無法指定關閉的問題,如有需強制顯示的彈窗,則會先判斷當前是否有顯示的彈窗,有則先 pop彈窗再顯示強制的彈窗

實現效果

Flutter 統一彈窗管理的思考與實現

解決辦法

DialogManager

Flutter 統一彈窗管理的思考與實現

統一彈窗管理類,管理彈窗的新增及顯示,詳細程式碼見最後

  • 初始化方法

  初始化方法中生成兩個陣列List _dialogList,_hasShowBeans。_dialogList用於儲存加入在統一彈窗中的彈窗資料 _hasShowBeans用於儲存已經顯示過,還沒有清除的彈窗資料

  為什麼不用單一 bean 去儲存顯示的彈窗資料,因為置為空的處理,是在彈窗消失的非同步回撥中,如果一個新的彈窗要顯示,舊的那個彈窗消失的回撥過來會把當前在顯示的彈窗資料置為空,則會導致當前判斷時否有顯示的彈窗不準確。

  • add 方法

Flutter 統一彈窗管理的思考與實現
主要用於新增彈窗資料在_dialogList中

  1. 假如新增的彈窗為 highClear 或 highClearAll,則會移除其它低優先順序的彈窗。主要場景是在強制升級彈窗、多終端登入彈窗
  2. 對於重複新增的彈窗,則不重複新增
  3. 對於棧中已經存在 highClear 或 highClearAll級別彈窗,則不新增低優先順序彈窗
  4. 對所有的彈窗進行優先順序排序
  • show 方法

主要用於新增彈窗資料在_dialogList中

  1. 假如新增的彈窗為 highClear 或 highClearAll,則會移除其它低優先順序的彈窗。主要場景是在強制升級彈窗、多終端登入彈窗
  2. 對於重複新增的彈窗,則不重複新增
  3. 對於棧中已經存在 highClear 或 highClearAll級別彈窗,則不新增低優先順序彈窗
  4. 對所有的彈窗進行優先順序排序

DialogBean

///dialog唯一標識,通過 DialogBean 資料內容生成 Md5生成
String dialogId;

///當前 dialog,顯示的檢視。如果為空,則在頂層頁面
String pageRouter;

///優先順序、用於顯示彈窗前排序,
///但對於加入的彈窗,已經顯示的情況
///[highClear] 清除已顯示的彈窗,直接顯示當前彈窗,該屬性慎用
///[high] 回收當前已顯示的彈窗,再顯示高優先順序彈窗,該屬性慎用
DialogPriority dialogPriority;

///用於排序
int priority;

///彈窗內部業務 widget,每次show 時動態建立。不能直接傳建立好的 widget,因為在 high回收時,呼叫 pop 再 show 會出現 The following NoSuchMethodError was thrown building Builder(dirty):
CreateDialogWidget createDialogWidg

複製程式碼

統一的彈窗實體類,詳細程式碼見最後

  1. dialogId,用於彈窗去重
  2. pageRouter用於指定彈窗需顯示的頁面,如果沒有指定,則直接顯示
  3. dialogPriority標識彈窗優先順序
  4. priority彈窗優先順序擴充套件欄位,主要用於排序
  5. createDialogWidget生成彈窗widget.不用直接使用 widget,在 high 回收時,再顯示會報異常。

DialogUtil

Flutter 統一彈窗管理的思考與實現
公共彈窗建立類,詳細程式碼見最後

  1. 生成對應 dialog 唯一的 id
  2. dialog 業務自身內部處理
  3. 需要加入統一彈窗管理中 dialog 統一在這裡生成

場景驗證

1. 多個彈窗是否按順序依次彈出

DialogManager()
  ..add(
      DialogBean(
        dialogPriority: DialogPriority.high,
        createDialogWidget: () =>
            DialogUtil.createTipWidget(context, "測試彈窗\n 換行
"),
      ))
      ..add(
      DialogBean(
        dialogPriority: DialogPriority.high,
        createDialogWidget: () =>
            DialogUtil.createTipWidget(context, "測試彈窗"),
      ))
  ..show(context);

複製程式碼

相同的彈窗是否去重

DialogManager()
  ..add(
      DialogBean(
        dialogPriority: DialogPriority.high,
        createDialogWidget: () =>
            DialogUtil.createTipWidget(context, "測試彈窗"),
      ))
      ..add(
      DialogBean(
        dialogPriority: DialogPriority.high,
        createDialogWidget: () =>
            DialogUtil.createTipWidget(context, "測試彈窗"),
      ))
  ..show(context);

複製程式碼

彈窗回收:已彈出低優先順序彈窗,顯示高優先順序彈窗,是否可以回收顯示

DialogManager()
  ..add(
      DialogBean(
        createDialogWidget: () => DialogUtil.createTipWidget(
          context,
          "測試彈窗\n 換行",
        ),
      ))
  ..show(context);

Future.delayed(Duration(seconds: 1), () {
  DialogManager()
    ..add(
        DialogBean(
          dialogPriority: DialogPriority.high,
          createDialogWidget: () =>
              DialogUtil.createTipWidget(context, "測試彈窗"),
        ))
    ..show(context);
});


複製程式碼

可反過來,先顯示高優先順序,再顯示其它

彈窗清除:已彈出低優先順序彈窗,顯示高優先順序並清除其它的彈窗

DialogManager()
  ..add(
      DialogBean(
        createDialogWidget: () => DialogUtil.createTipWidget(
          context,
          "測試彈窗\n 換行"
        ),
      ))
  ..show(context);

Future.delayed(Duration(seconds: 1), () {
  DialogManager()
    ..add(
        DialogBean(
          dialogPriority: DialogPriority.highClear,
          createDialogWidget: () =>
              DialogUtil.createTipWidget(context, "測試彈窗"),
        ))
    ..show(context);
});

複製程式碼

彈窗頁面驗證:指定彈窗在指定頁面顯示


Future.delayed(Duration(seconds: 1), () {
  DialogManager()
    ..add(
        DialogBean(
          pageRouter: Router.login,
          createDialogWidget: () => DialogUtil.createTipWidget(
            context,
            "測試彈窗",
          ),
        ))
    ..show(context);
});

複製程式碼

最後

  如果在使用過程遇到問題,歡迎下方留言交流。

  程式碼下載地址

學習資料

請大家不吝點贊!因為您的點贊是對我最大的鼓勵,謝謝!

相關文章