SmartDialog遷移至4.0:一份真誠的遷移說明

前言

一個開源庫,隨着不斷的迭代優化,難免會遇到一個很痛苦的問題

  • 最初的設計並不是很合理:想添加的很多新功能都受此掣肘

想使得該庫更加的強大和健壯,必須要做一個重構

  • 因為重構涉及到對外暴露的api,所以大家會遇到一個比較煩躁的問題:更新版本後,會大面積報錯
  • 我考慮了很久,到底怎麼幫大家快速遷移呢?最終想到了一個還算合理的方案

對於flutter_smart_dialog 4.0版本的改動,很多是為了解決自己以前考慮不周的歷史遺留,以前這個庫的初心,主要是為了解決loading和dialog穿透問題;現在擴展到:custom dialog,attach dialog,loading,toast,最初的設計真的力不從心了,config中的api難以去細分的控制這四個模塊功能,一些參數的設計基於現在的功能和場景也不太合理等等

希望大家能夠理解我為什麼要重構🥺,我絕對不是在搞事情🥺

快速遷移指南

兼容API(必須)⭐️

說明

  • show方法快速兼容
SmartDialog.compatible.show();
SmartDialog.compatible.showAttach();
SmartDialog.compatible.showLoading();
SmartDialog.compatible.showToast();
  • config快速兼容
SmartDialog.compatible.config;

增加compatible中間變量,可快速兼容改動的各種參數

快速操作

  • 使用全局替換功能快速遷移:SmartDialog.show —> SmartDialog.compatible.show
    • Mac:command + shift + r
    • Windows:ctrl + shift + r

image-20220501230620406

  • Config:SmartDialog.config —> SmartDialog.compatible.config
    • Mac:command + shift + r
    • Windows:ctrl + shift + r

image-20220501230830221

參數移除(必須)⭐️

  • 4.0版本刪除了少量參數
方法 說明
showLoading(…) 刪除background參數(compatible不兼容該參數)
showToast(…) 刪除alignment參數(compatible不兼容該參數)
showAttach(…) 刪除highlight參數(compatible兼容該參數)
  • 刪除了這些參數,初始化自定義loading和toast的時候,需要做一點點調整
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomePage,
      // here
      navigatorObservers: [FlutterSmartDialog.observer],
      // here
      builder: FlutterSmartDialog.init(
        //default toast widget
        toastBuilder: (String msg) => CustomToastWidget(msg: msg),
        //default loading widget
        loadingBuilder: (String msg) => CustomLoadingWidget(msg: msg),
      ),
    );
  }
}

說明

backgroundalignment這倆個參數實在沒什麼用,用到的頻率實在太低了

一般都是自定義toast和loading樣式,想怎麼畫就怎麼畫;如果只是簡單用下toast和loading,這倆個參數做不到很強的自定義效果,實在過於累贅,索性刪除了

參數名變動(可選)

通過上面兼容API參數移除就可以完整遷移了

這裡我將變動的參數名完整的寫下,大家可以對照下

原參數名 變動參數名
widget builder:和路由dialog參數對齊(具體見下面builder參數說明)
isLoading / isLoadingTemp animationType:方便後期擴展多種動畫類型
isPenetrate / isPenetrateTemp usePenetrate:true(點擊事件將穿透背景),false(不穿透)
isUseAnimation / isUseAnimationTemp useAnimation:true(使用動畫),false(不使用)
clickBgDismiss / clickBgDismissTemp clickMaskDismiss:true(點擊遮罩後,關閉dialog),false(不關閉)
animationDuration / animationDurationTemp animationTime:動畫持續時間
alignmentTemp alignment:控制彈窗的位置
maskColorTemp maskColor:遮罩顏色
maskWidgetTemp maskWidget:可高度定製遮罩
debounceTemp debounce:防抖功能

builder參數說明(重要)

4.0版本對自定義控件參數做了很大改變

  • 老版本
SmartDialog.show(
  widget: Container(
    height: 80,
    width: 180,
    decoration: BoxDecoration(
      color: Colors.black,
      borderRadius: BorderRadius.circular(10),
    ),
    alignment: Alignment.center,
    child: Text(
      'easy custom dialog',
      style: TextStyle(color: Colors.white),
    ),
  ),
);
  • 4.0版本
SmartDialog.show(builder: (context) {
  return Container(
    height: 80,
    width: 180,
    decoration: BoxDecoration(
      color: Colors.black,
      borderRadius: BorderRadius.circular(10),
    ),
    alignment: Alignment.center,
    child: Text(
      'easy custom dialog',
      style: TextStyle(color: Colors.white),
    ),
  );
});

這個改動雖然會讓使用麻煩了一點,但是有很重要的意義

  • 首先是為了和路由dialog的api對齊,路由dialog自定義控件參數也是builder
  • 然後解決自定義dialog自身動態刷新問題:自定義布局有TextField,鍵盤彈起的時候,自定義dialog布局可以動態調整距離(需要使用相應widget)

4.0版本新增功能

強大的Config

  • 可以使用config獲取dialog是否存在情況
// 自定義dialog,attach或loading,是否存在在界面上
SmartDialog.config.isExist;
// 自定義dialog或attach是否存在在界面上
SmartDialog.config.isExistDialog;
// loading是否存在界面上
SmartDialog.config.isExistLoading;
// toast是否存在在界面上
SmartDialog.config.isExistToast;
  • config可以更細緻的控制show,showAttach,showLoading,showToast等彈窗
    • SmartConfigXxx()默認參數都是我經過深思後設置的,無特殊要求可以不用額外設置
    • 如果不需要自定義config數值,下方初始化代碼無需寫
SmartDialog.config
  ..custom = SmartConfigCustom()
  ..attach = SmartConfigAttach()
  ..loading = SmartConfigLoading()
  ..toast = SmartConfigToast();
  • 可以自定任意config中的數值,以滿足相應的需求
    • 下方代碼是演示自定義參數
    • 大家可以按需設置
SmartDialog.config
  ..custom = SmartConfigCustom(
    maskColor: Colors.black.withOpacity(0.35),
    useAnimation: true,
  )
  ..attach = SmartConfigAttach(
    animationType: SmartAnimationType.scale,
    usePenetrate: false,
  )
  ..loading = SmartConfigLoading(
    clickMaskDismiss: false,
    leastLoadingTime: const Duration(milliseconds: 0),
  )
  ..toast = SmartConfigToast(
    intervalTime: const Duration(milliseconds: 100),
    displayTime: const Duration(milliseconds: 2000),
  );

bindPage

說明

這個參數的含義是將SmartDialog將page綁定:如果在SmartDialog上跳轉頁面

  • 和當前頁面綁定SmartDialog會自動隱藏
  • 回到綁定頁面的時候,SmartDialog將會顯示

關於在Dialog上面跳轉頁面的問題,4.0之前的版本,可以使用useSystem參數解決

  • 使用useSystem參數時,本質是使用自帶dialog作為載體,這樣就可以合理的和page交互
  • 但是因為自帶dialog的各種局限,使用useSystem時:usePenetratetagKeepSinglepermanent都被禁止使用了

4.0版本引入的bindPage的邏輯,可以避免使用useSystem時的各種限制

bindPage是默認開啟的(可在config中配置),也可以在使用show和showAttach時手動關閉或開啟;在特殊的業務場景,按需使用bindPageuseSystem即可

使用效果

  • 寫個演示demo,這個就是正常在彈窗上跳轉頁面操作
void _dialogBindPage() async {
  var index = 0;
  Function()? showDialog;

  toNewPage(bool useSystem) async {
    Get.to(
      () {
        return Scaffold(
          appBar: AppBar(title: Text('New Page ${++index}')),
          body: Container(
            color: randomColor(),
            alignment: Alignment.center,
            child: ElevatedButton(
              onPressed: () => showDialog?.call(),
              child: Text('test bindPage $index'),
            ),
          ),
        );
      },
      preventDuplicates: false,
    );
  }

  showDialog = () {
    SmartDialog.show(builder: (_) {
      return Container(
        width: 300,
        height: 170,
        alignment: Alignment.center,
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(20),
        ),
        child: ElevatedButton(
          onPressed: () => toNewPage(false),
          child: Text('test bindPage $index'),
        ),
      );
    });
  };

  showDialog();
}
  • 來看看效果
    • 老實說,沒有使用useSystem功能時的效果絲滑
    • 但是bindPage也解決了彈窗上跳轉頁面的問題,同時又保留了usePenetrate,tag,KeepSingle,permanent等功能
    • 大家按需使用

bindPage

關閉彈窗時攜帶數據

該功能和flutter路由關閉,攜帶返回數據功能對齊

  • 看下demo:點擊show result按鈕,關閉彈窗,並將輸入框中的數據返回
void _dialogCarryResult() async {
  var result = await SmartDialog.show(
    builder: (_) {
      var message = '';
      return Container(
        width: 300,
        height: 170,
        alignment: Alignment.center,
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(20),
        ),
        child: Column(mainAxisSize: MainAxisSize.min, children: [
          Container(
            width: 100,
            margin: EdgeInsets.only(bottom: 30),
            child: TextField(onChanged: (msg) => message = msg),
          ),
          ElevatedButton(
            onPressed: () => SmartDialog.dismiss(result: message),
            child: Text('show result'),
          )
        ]),
      );
    },
  );

  SmartDialog.showToast("$result");
}
  • 效果

carryResult

永久化Dialog

permanent參數設置成true,打開的dialog將變成永久化dialog,框架內部所做的所有兜底關閉操作(返回事件,路由pop,點擊遮罩等)將失效,只能手動關閉

該功能請結合實際業務場景使用,請勿濫用

  • 打開一個永久化dialog
SmartDialog.show(
  permanent: true,
  usePenetrate: true,
  builder: (_) => Container(width: 150, height: 150, color: Colors.black),
);
  • 關閉永久化dialog
SmartDialog.dismiss(force: true);
  • 來看下demo
void _dialogPermanent() async {
  openPermanentDialog() {
    SmartDialog.show(
      permanent: true,
      alignment: Alignment.centerRight,
      usePenetrate: true,
      clickMaskDismiss: false,
      builder: (_) {
        return Container(
          width: 150,
          height: double.infinity,
          alignment: Alignment.center,
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.only(
              topLeft: Radius.circular(20),
              bottomLeft: Radius.circular(20),
            ),
            boxShadow: [
              BoxShadow(color: Colors.grey, blurRadius: 8, spreadRadius: 0.2)
            ],
          ),
          child: Text('permanent dialog'),
        );
      },
    );
  }

  SmartDialog.show(builder: (_) {
    return Container(
      width: 300,
      height: 170,
      alignment: Alignment.center,
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(20),
      ),
      child: Wrap(spacing: 20, children: [
        ElevatedButton(
          onPressed: () => openPermanentDialog(),
          child: Text('open'),
        ),
        ElevatedButton(
          onPressed: () => SmartDialog.dismiss(force: true),
          child: Text('close'),
        )
      ]),
    );
  });
}
  • 效果:可以看見pop路由,點擊遮罩和返回事件,都不能關閉permanent dialog

permanentDialog

最小加載時間

config中loading中有個leastLoadingTime參數,可以控制最小的加載時間

這個功能是為了解決接口請求太快,導致loading彈窗一閃而過的問題

  • 使用:該參數請結合實際業務場景設置合適的數據,leastLoadingTime默認數值為0秒
    • 此處僅做演示,才在此處給config.loading重新賦值,一般建議在app初始化位置就定好參數
    • showLoading()之後立馬調用dismiss,loading會一閃而過
    • 設置了leastLoadingTime為2秒,loading會強制等待倆秒之後,dismiss才會生效
void _loadingLeastTime() async {
  SmartDialog.config.loading = SmartConfigLoading(
    leastLoadingTime: const Duration(seconds: 2),
  );
  SmartDialog.showLoading();
  SmartDialog.dismiss();
  SmartDialog.config.loading = SmartConfigLoading();
}
  • 效果

leastTime

連續toast顯示間隔時間

當多個toast連續顯示的時候,前一個toast和後一個toast顯示無間隔時間,看起來有點突兀

此處在SmartConfigToast中增加了一個intervalTime參數,用以控制間隔時間

默認的intervalTime已經是個合理參數,如無必要,最好不要更改

  • 來看下效果,僅做演示,intervalTime數值就設置稍微大一些
void _toastIntervalTime() async {
  SmartDialog.config.toast = SmartConfigToast(
    intervalTime: const Duration(milliseconds: 800),
  );
  for (var i = 0; i < 3; i++) {
    SmartDialog.showToast("toast $i").then((value) {
      if (!SmartDialog.config.isExistToast) {
        SmartDialog.config.toast = SmartConfigToast();
      }
    });
  }
}
  • 效果圖

intervalTime

總結

SmartDialog 4.0版本是個非常重要的版本,標誌着SmartDialog告別了羞澀,走向了成熟

經過這次重構,我也有了信心,去面對更加複雜的業務場景,進行各種拓展

這次重構我做了很多思考,也非常感謝大家給我提個各種issues,是你們啟發了我!

img

Tags: