Flutter EasyLoading - 讓全局Toast/Loading更簡單

?flutter_easyloading: 一個(gè)簡單易用的Flutter插件,包含23種loading動畫效果仇冯、進(jìn)度條展示进萄、Toast展示。純Flutter端實(shí)現(xiàn)磁玉,支持iOS停忿、Android。

?開源地址https://github.com/huangjianke/flutter_easyloading

前言

FlutterGoogle在2017年推出的一套開源跨平臺UI框架蚊伞,可以快速地在iOS席赂、AndroidWeb平臺上構(gòu)建高質(zhì)量的原生用戶界面。Flutter發(fā)布至今时迫,不可謂不說是大受追捧颅停,吸引了大批App原生開發(fā)者、Web開發(fā)者前赴后繼的投入其懷抱掠拳,也正由于Flutter是跨平臺領(lǐng)域的新星癞揉,總的來說,其生態(tài)目前還不是十分完善溺欧,我相信對于習(xí)慣了原生開發(fā)的同學(xué)們來說喊熟,找輪子肯定沒有了那種章手就萊的感覺。比如說這篇文章即將講到的姐刁,如何在Flutter應(yīng)用內(nèi)簡單芥牌、方便的展示Toast或者Loading框呢?

探索

起初龙填,我也在pub上找到了幾個(gè)比較優(yōu)秀的插件:

  • FlutterToast: 這個(gè)插件應(yīng)該是很多剛?cè)肟?code>Flutter的同學(xué)們都使用過的胳泉,它依賴于原生,但對于UI層級的問題岩遗,最好在Flutter端解決扇商,這樣便于后期維護(hù),也可以減少兼容性問題宿礁;
  • flutter_oktoast: 純Flutter端實(shí)現(xiàn)案铺,調(diào)用方便。但缺少loading梆靖、進(jìn)度條展示控汉,仍可自定義實(shí)現(xiàn);

試用過后返吻,發(fā)現(xiàn)這些插件都或多或少不能滿足我們的產(chǎn)品需求姑子,于是便結(jié)合自己產(chǎn)品的需求來造了這么個(gè)輪子,也希望可以幫到有需要的同學(xué)們测僵。效果預(yù)覽:

flutter_easyloading

實(shí)現(xiàn)

showDialog 實(shí)現(xiàn)

先看看初期我們實(shí)現(xiàn)彈窗的方式showDialog街佑,部分源碼如下:

Future<T> showDialog<T>({
  @required BuildContext context,
  bool barrierDismissible = true,
  @Deprecated(
    'Instead of using the "child" argument, return the child from a closure '
    'provided to the "builder" argument. This will ensure that the BuildContext '
    'is appropriate for widgets built in the dialog. '
    'This feature was deprecated after v0.2.3.'
  )
  Widget child,
  WidgetBuilder builder,
  bool useRootNavigator = true,
})

這里有個(gè)必傳參數(shù)context谢翎,想必接觸過Flutter開發(fā)一段時(shí)間的同學(xué),都會對BuildContext有所了解沐旨。簡單來說BuildContext就是構(gòu)建Widget中的應(yīng)用上下文森逮,是Flutter的重要組成部分。BuildContext只出現(xiàn)在兩個(gè)地方:

  • StatelessWidget.build方法中:創(chuàng)建StatelessWidgetbuild方法
  • State對象中:創(chuàng)建StatefulWidgetState對象的build方法中磁携,另一個(gè)是State的成員變量

有關(guān)BuildContext更深入的探討不在此文的探討范圍內(nèi)褒侧,如果使用showDialog實(shí)現(xiàn)彈窗操作,那么我們所考慮的問題便是谊迄,如何方便快捷的在任意地方去獲取BuildContext闷供,從而實(shí)現(xiàn)彈窗。如果有同學(xué)恰巧也用了showDialog這種方式的話鳞上,我相信这吻,你也會發(fā)現(xiàn),在任意地方獲取BuildContext并不是那么簡單篙议,而且會產(chǎn)生很多不必要的代碼量唾糯。

那么,我們就只能使用這種體驗(yàn)極其不友好的方法么鬼贱?

當(dāng)然不是的移怯,請繼續(xù)看。

Flutter EasyLoading 介紹

Flutter EasyLoading是一個(gè)簡單易用的Flutter插件这难,包含23loading動畫效果舟误、進(jìn)度條展示、Toast展示姻乓。純Flutter端實(shí)現(xiàn)嵌溢,兼容性好,支持iOS蹋岩、Android赖草。先簡單看下如何使用Flutter EasyLoading

安裝

將以下代碼添加到您項(xiàng)目中的 pubspec.yaml 文件:

dependencies:
  flutter_easyloading: ^1.1.0 // 請使用最新版

導(dǎo)入

import 'package:flutter_easyloading/flutter_easyloading.dart';

如何使用

首先, 使用 FlutterEasyLoading 組件包裹您的App組件:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    /// 子組件通常為 [MaterialApp] 或者 [CupertinoApp].
    /// 這樣做是為了確保 loading 組件能覆蓋在其他組件之上.
    return FlutterEasyLoading(
      child: MaterialApp(
        title: 'Flutter EasyLoading',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(title: 'Flutter EasyLoading'),
      ),
    );
  }
}

然后, 請盡情使用吧:

EasyLoading.show(status: 'loading...'); 

EasyLoading.showProgress(0.3, status: 'downloading...');

EasyLoading.showSuccess('Great Success!');

EasyLoading.showError('Failed with Error');

EasyLoading.showInfo('Useful Information.');

EasyLoading.dismiss();

自定義樣式

首先剪个,我們看下Flutter EasyLoading目前支持的自定義屬性:

/// loading的樣式, 默認(rèn)[EasyLoadingStyle.dark].
EasyLoadingStyle loadingStyle;

/// loading的指示器類型, 默認(rèn)[EasyLoadingIndicatorType.fadingCircle].
EasyLoadingIndicatorType indicatorType;

/// loading的遮罩類型, 默認(rèn)[EasyLoadingMaskType.none].
EasyLoadingMaskType maskType;

/// 文本的對齊方式 , 默認(rèn)[TextAlign.center].
TextAlign textAlign;

/// loading內(nèi)容區(qū)域的內(nèi)邊距.
EdgeInsets contentPadding;

/// 文本的內(nèi)邊距.
EdgeInsets textPadding;

/// 指示器的大小, 默認(rèn)40.0.
double indicatorSize;

/// loading的圓角大小, 默認(rèn)5.0.
double radius;

/// 文本大小, 默認(rèn)15.0.
double fontSize;

/// 進(jìn)度條指示器的寬度, 默認(rèn)2.0.
double progressWidth;

/// [showSuccess] [showError] [showInfo]的展示時(shí)間, 默認(rèn)2000ms.
Duration displayDuration;

/// 文本的顏色, 僅對[EasyLoadingStyle.custom]有效.
Color textColor;

/// 指示器的顏色, 僅對[EasyLoadingStyle.custom]有效.
Color indicatorColor;

/// 進(jìn)度條指示器的顏色, 僅對[EasyLoadingStyle.custom]有效.
Color progressColor;

/// loading的背景色, 僅對[EasyLoadingStyle.custom]有效.
Color backgroundColor;

/// 遮罩的背景色, 僅對[EasyLoadingMaskType.custom]有效.
Color maskColor;

/// 當(dāng)loading展示的時(shí)候秧骑,是否允許用戶操作.
bool userInteractions;

/// 展示成功狀態(tài)的自定義組件
Widget successWidget;

/// 展示失敗狀態(tài)的自定義組件
Widget errorWidget;

/// 展示信息狀態(tài)的自定義組件
Widget infoWidget;

因?yàn)?EasyLoading 是一個(gè)全局單例, 所以我們可以在任意一個(gè)地方自定義它的樣式:

EasyLoading.instance
  ..displayDuration = const Duration(milliseconds: 2000)
  ..indicatorType = EasyLoadingIndicatorType.fadingCircle
  ..loadingStyle = EasyLoadingStyle.dark
  ..indicatorSize = 45.0
  ..radius = 10.0
  ..backgroundColor = Colors.green
  ..indicatorColor = Colors.yellow
  ..textColor = Colors.yellow
  ..maskColor = Colors.blue.withOpacity(0.5);

更多的指示器動畫類型可查看 flutter_spinkit showcase

可以看到,Flutter EasyLoading的集成以及使用相當(dāng)?shù)暮唵慰勰遥矣胸S富的自定義樣式乎折,總會有你滿意的。

接下來侵歇,我們來看看Flutter EasyLoading的代碼實(shí)現(xiàn)骂澄。

Flutter EasyLoading 的實(shí)現(xiàn)

本文將通過以下兩個(gè)知識點(diǎn)來介紹Flutter EasyLoading的主要實(shí)現(xiàn)過程及思路:

  • OverlayOverlayEntry實(shí)現(xiàn)全局彈窗
  • CustomPaintCanvas實(shí)現(xiàn)圓形進(jìn)度條繪制

Overlay惕虑、OverlayEntry 實(shí)現(xiàn)全局彈窗

先看看官方關(guān)于Overlay的描述:

/// A [Stack] of entries that can be managed independently.
///
/// Overlays let independent child widgets "float" visual elements on top of
/// other widgets by inserting them into the overlay's [Stack]. The overlay lets
/// each of these widgets manage their participation in the overlay using
/// [OverlayEntry] objects.
///
/// Although you can create an [Overlay] directly, it's most common to use the
/// overlay created by the [Navigator] in a [WidgetsApp] or a [MaterialApp]. The
/// navigator uses its overlay to manage the visual appearance of its routes.
///
/// See also:
///
///  * [OverlayEntry].
///  * [OverlayState].
///  * [WidgetsApp].
///  * [MaterialApp].
class Overlay extends StatefulWidget {}

也就是說坟冲,Overlay是一個(gè)StackWidget士修,可以將OverlayEntry插入到Overlay中,使獨(dú)立的child窗口懸浮于其他Widget之上樱衷。利用這個(gè)特性,我們可以用OverlayMaterialAppCupertinoApp包裹起來酒唉,這樣做的目的是為了確保 loading 組件能覆蓋在其他組件之上矩桂,因?yàn)樵?code>Flutter中只會存在一個(gè)MaterialAppCupertinoApp根節(jié)點(diǎn)組件。(注:這里的做法參考于flutter_oktoast插件痪伦,感謝)侄榴。

另外,這樣做的目的還可以解決另外一個(gè)核心問題:將 context 緩存到內(nèi)存中网沾,后續(xù)所有調(diào)用均不需要提供context癞蚕。實(shí)現(xiàn)如下:

@override
Widget build(BuildContext context) {
  return Directionality(
    child: Overlay(
      initialEntries: [
        OverlayEntry(
          builder: (BuildContext _context) {
            // 緩存 context
            EasyLoading.instance.context = _context;
            // 這里的child必須是MaterialApp或CupertinoApp
            return widget.child;
          },
        ),
      ],
    ),
    textDirection: widget.textDirection,
  );
}
// 創(chuàng)建OverlayEntry
OverlayEntry _overlayEntry = OverlayEntry(
  builder: (BuildContext context) => LoadingContainer(
    key: _key,
    status: status,
    indicator: w,
    animation: _animation,
  ),
);

// 將OverlayEntry插入到Overlay中
// 通過Overlay.of()我們可以獲取到App根節(jié)點(diǎn)的Overlay
Overlay.of(_getInstance().context).insert(_overlayEntry);

// 調(diào)用OverlayEntry自身的remove()方法,從所在的Overlay中移除自己
_overlayEntry.remove();

Overlay辉哥、OverlayEntry的使用及理解還是很簡單桦山,我們也可以再更多的使用場景使用他們,比如說醋旦,類似PopupWindow的彈窗效果恒水、全局自定義Dialog彈窗等等。只要靈活運(yùn)用饲齐,我們可以實(shí)現(xiàn)很多我們想要的效果钉凌。

CustomPaintCanvas實(shí)現(xiàn)圓形進(jìn)度條繪制

幾乎所有的UI系統(tǒng)都會提供一個(gè)自繪UI的接口,這個(gè)接口通常會提供一塊2D畫布Canvas捂人,Canvas內(nèi)部封裝了一些基本繪制的API御雕,我們可以通過Canvas繪制各種自定義圖形。在Flutter中滥搭,提供了一個(gè)CustomPaint組件酸纲,它可以結(jié)合一個(gè)畫筆CustomPainter來實(shí)現(xiàn)繪制自定義圖形。接下來我將簡單介紹下圓形進(jìn)度條的實(shí)現(xiàn)论熙。

我們先來看看CustomPaint構(gòu)造函數(shù):

const CustomPaint({
  Key key,
  this.painter,
  this.foregroundPainter,
  this.size = Size.zero,
  this.isComplex = false,
  this.willChange = false,
  Widget child,
})
  • painter: 背景畫筆福青,會顯示在子節(jié)點(diǎn)后面;
  • foregroundPainter: 前景畫筆,會顯示在子節(jié)點(diǎn)前面
  • size:當(dāng)childnull時(shí)脓诡,代表默認(rèn)繪制區(qū)域大小无午,如果有child則忽略此參數(shù),畫布尺寸則為child尺寸祝谚。如果有child但是想指定畫布為特定大小宪迟,可以使用SizeBox包裹CustomPaint實(shí)現(xiàn)。
  • isComplex:是否復(fù)雜的繪制交惯,如果是次泽,Flutter會應(yīng)用一些緩存策略來減少重復(fù)渲染的開銷穿仪。
  • willChange:和isComplex配合使用,當(dāng)啟用緩存時(shí)意荤,該屬性代表在下一幀中繪制是否會改變啊片。

可以看到,繪制時(shí)我們需要提供前景或背景畫筆玖像,兩者也可以同時(shí)提供紫谷。我們的畫筆需要繼承CustomPainter類,我們在畫筆類中實(shí)現(xiàn)真正的繪制邏輯捐寥。

接下來笤昨,我們看下怎么通過CustomPainter繪制圓形進(jìn)度條:

class _CirclePainter extends CustomPainter {
  final Color color;
  final double value;
  final double width;

  _CirclePainter({
    @required this.color,
    @required this.value,
    @required this.width,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = color
      ..strokeWidth = width
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round;
    canvas.drawArc(
      Offset.zero & size,
      -math.pi / 2,
      math.pi * 2 * value,
      false,
      paint,
    );
  }

  @override
  bool shouldRepaint(_CirclePainter oldDelegate) => value != oldDelegate.value;
}

從上面我們可以看到,CustomPainter中定義了一個(gè)虛函數(shù)paint:

void paint(Canvas canvas, Size size);

這個(gè)函數(shù)是繪制的核心所在握恳,它包含了以下兩個(gè)參數(shù):

  • canvas: 畫布瞒窒,包括各種繪制方法, 如 drawLine(畫線)drawRect(畫矩形)乡洼、drawCircle(畫圓)
  • size: 當(dāng)前繪制區(qū)域大小

畫布現(xiàn)在有了崇裁,那么接下來我們就需要一支畫筆了。Flutter提供了Paint類來實(shí)現(xiàn)畫筆束昵。而且可以配置畫筆的各種屬性如粗細(xì)寇壳、顏色、樣式等妻怎,比如:

final paint = Paint()
  ..color = color // 顏色
  ..strokeWidth = width // 寬度
  ..style = PaintingStyle.stroke
  ..strokeCap = StrokeCap.round;

最后壳炎,我們就是需要使用drawArc方法進(jìn)行圓弧的繪制了:

canvas.drawArc(
  Offset.zero & size,
  -math.pi / 2,
  math.pi * 2 * value,
  false,
  paint,
);

到此,我們就完成了進(jìn)度條的繪制逼侦。另外我們也需要注意下繪制性能問題匿辩。好在類中提供了重寫shouldRepaint的方法,這個(gè)方法決定了畫布什么時(shí)候會重新繪制榛丢,在復(fù)雜的繪制中對提升繪制性能是相當(dāng)有成效的铲球。

@override
bool shouldRepaint(_CirclePainter oldDelegate) => value != oldDelegate.value;

結(jié)語

毫無疑問,Flutter的前景是一片光明的晰赞,也許現(xiàn)在還存在諸多問題稼病,但我相信更多的人會愿意陪著Flutter一起成長。期待著Flutter的生態(tài)圈的完善掖鱼。后期我也會逐步完善Flutter EasyLoading然走,期待您的寶貴意見。

最后戏挡,希望Flutter EasyLoading對您有所幫助芍瑞。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市褐墅,隨后出現(xiàn)的幾起案子拆檬,更是在濱河造成了極大的恐慌洪己,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件竟贯,死亡現(xiàn)場離奇詭異答捕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)屑那,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進(jìn)店門噪珊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人齐莲,你說我怎么就攤上這事×谆” “怎么了选酗?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長岳枷。 經(jīng)常有香客問我芒填,道長,這世上最難降的妖魔是什么空繁? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任殿衰,我火速辦了婚禮,結(jié)果婚禮上盛泡,老公的妹妹穿的比我還像新娘闷祥。我一直安慰自己,他們只是感情好傲诵,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布凯砍。 她就那樣靜靜地躺著,像睡著了一般拴竹。 火紅的嫁衣襯著肌膚如雪悟衩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天栓拜,我揣著相機(jī)與錄音座泳,去河邊找鬼。 笑死幕与,一個(gè)胖子當(dāng)著我的面吹牛挑势,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播啦鸣,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼薛耻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了赏陵?” 一聲冷哼從身側(cè)響起饼齿,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤饲漾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后缕溉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體考传,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年证鸥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了僚楞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,852評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡枉层,死狀恐怖泉褐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鸟蜡,我是刑警寧澤膜赃,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站揉忘,受9級特大地震影響跳座,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泣矛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一疲眷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧您朽,春花似錦狂丝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至魂奥,卻和暖如春菠剩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背耻煤。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工具壮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哈蝇。 一個(gè)月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓棺妓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親炮赦。 傳聞我的和親對象是個(gè)殘疾皇子怜跑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內(nèi)容