✨ flutter_easyloading: 一個簡單易用的 Flutter插件,包含23種loading動畫效果、進度條展現、Toast展現。純Flutter端實現,支持iOS、Android。✨開源地址:https://github.com/huangjiank...git
Flutter
是Google
在2017年推出的一套開源跨平臺UI
框架,能夠快速地在iOS
、Android
和Web
平臺上構建高質量的原生用戶界面。Flutter
發佈至今,不可謂不說是大受追捧,吸引了大批App
原生開發者、Web
開發者前赴後繼的投入其懷抱,也正因爲Flutter
是跨平臺領域的新星,總的來講,其生態目前還不是十分完善,我相信對於習慣了原生開發的同窗們來講,找輪子確定沒有了那種章手就萊的感受。好比說這篇文章即將講到的,如何在Flutter
應用內簡單、方便的展現Toast
或者Loading
框呢?github
起初,我也在pub上找到了幾個比較優秀的插件:canvas
Flutter
的同窗們都使用過的,它依賴於原生,但對於UI層級的問題,最好在Flutter端解決,這樣便於後期維護,也能夠減小兼容性問題;Flutter
端實現,調用方便。但缺乏loading
、進度條展現,仍可自定義實現;試用事後,發現這些插件都或多或少不能知足咱們的產品需求,因而便結合本身產品的需求來造了這麼個輪子,也但願能夠幫到有須要的同窗們。效果預覽:緩存
先看看初期咱們實現彈窗的方式showDialog
,部分源碼以下:app
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, })
這裏有個必傳參數context
,想必接觸過Flutter
開發一段時間的同窗,都會對BuildContext
有所瞭解。簡單來講BuildContext
就是構建Widget
中的應用上下文,是Flutter
的重要組成部分。BuildContext
只出如今兩個地方:框架
StatelessWidget.build
方法中:建立StatelessWidget
的build
方法State
對象中:建立StatefulWidget
的State
對象的build
方法中,另外一個是State
的成員變量有關BuildContext
更深刻的探討不在此文的探討範圍內,若是使用showDialog
實現彈窗操做,那麼咱們所考慮的問題即是,如何方便快捷的在任意地方去獲取BuildContext
,從而實現彈窗。若是有同窗恰巧也用了showDialog
這種方式的話,我相信,你也會發現,在任意地方獲取BuildContext
並非那麼簡單,並且會產生不少沒必要要的代碼量。less
那麼,咱們就只能使用這種體驗極其不友好的方法麼?ide
固然不是的,請繼續看。函數
Flutter EasyLoading
是一個簡單易用的Flutter
插件,包含23種loading
動畫效果、進度條展現、Toast
展現。純Flutter
端實現,兼容性好,支持iOS
、Android
。先簡單看下如何使用Flutter EasyLoading
。性能
將如下代碼添加到您項目中的 pubspec.yaml
文件:
dependencies: flutter_easyloading: ^1.1.0 // 請使用最新版
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的樣式, 默認[EasyLoadingStyle.dark]. EasyLoadingStyle loadingStyle; /// loading的指示器類型, 默認[EasyLoadingIndicatorType.fadingCircle]. EasyLoadingIndicatorType indicatorType; /// loading的遮罩類型, 默認[EasyLoadingMaskType.none]. EasyLoadingMaskType maskType; /// 文本的對齊方式 , 默認[TextAlign.center]. TextAlign textAlign; /// loading內容區域的內邊距. EdgeInsets contentPadding; /// 文本的內邊距. EdgeInsets textPadding; /// 指示器的大小, 默認40.0. double indicatorSize; /// loading的圓角大小, 默認5.0. double radius; /// 文本大小, 默認15.0. double fontSize; /// 進度條指示器的寬度, 默認2.0. double progressWidth; /// [showSuccess] [showError] [showInfo]的展現時間, 默認2000ms. Duration displayDuration; /// 文本的顏色, 僅對[EasyLoadingStyle.custom]有效. Color textColor; /// 指示器的顏色, 僅對[EasyLoadingStyle.custom]有效. Color indicatorColor; /// 進度條指示器的顏色, 僅對[EasyLoadingStyle.custom]有效. Color progressColor; /// loading的背景色, 僅對[EasyLoadingStyle.custom]有效. Color backgroundColor; /// 遮罩的背景色, 僅對[EasyLoadingMaskType.custom]有效. Color maskColor; /// 當loading展現的時候,是否容許用戶操做. bool userInteractions; /// 展現成功狀態的自定義組件 Widget successWidget; /// 展現失敗狀態的自定義組件 Widget errorWidget; /// 展現信息狀態的自定義組件 Widget infoWidget;
由於 EasyLoading
是一個全局單例, 因此咱們能夠在任意一個地方自定義它的樣式:
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
的集成以及使用至關的簡單,並且有豐富的自定義樣式,總會有你滿意的。
接下來,咱們來看看Flutter EasyLoading
的代碼實現。
本文將經過如下兩個知識點來介紹Flutter EasyLoading
的主要實現過程及思路:
Overlay
、OverlayEntry
實現全局彈窗CustomPaint
與Canvas
實現圓形進度條繪製先看看官方關於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
是一個Stack
的Widget
,能夠將OverlayEntry
插入到Overlay
中,使獨立的child
窗口懸浮於其餘Widget
之上。利用這個特性,咱們能夠用Overlay
將 MaterialApp
或CupertinoApp
包裹起來,這樣作的目的是爲了確保 loading
組件能覆蓋在其餘組件之上,由於在Flutter
中只會存在一個MaterialApp
或CupertinoApp
根節點組件。(注:這裏的作法參考於flutter_oktoast插件,感謝)。
另外,這樣作的目的還能夠解決另一個核心問題:將 context
緩存到內存中,後續全部調用均不須要提供context
。實現以下:
@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, ); }
// 建立OverlayEntry OverlayEntry _overlayEntry = OverlayEntry( builder: (BuildContext context) => LoadingContainer( key: _key, status: status, indicator: w, animation: _animation, ), ); // 將OverlayEntry插入到Overlay中 // 經過Overlay.of()咱們能夠獲取到App根節點的Overlay Overlay.of(_getInstance().context).insert(_overlayEntry); // 調用OverlayEntry自身的remove()方法,從所在的Overlay中移除本身 _overlayEntry.remove();
Overlay
、OverlayEntry
的使用及理解仍是很簡單,咱們也能夠再更多的使用場景使用他們,好比說,相似PopupWindow
的彈窗效果、全局自定義Dialog
彈窗等等。只要靈活運用,咱們能夠實現不少咱們想要的效果。
CustomPaint
與Canvas
實現圓形進度條繪製幾乎全部的UI
系統都會提供一個自繪UI
的接口,這個接口一般會提供一塊2D
畫布Canvas
,Canvas
內部封裝了一些基本繪製的API
,咱們能夠經過Canvas
繪製各類自定義圖形。在Flutter
中,提供了一個CustomPaint
組件,它能夠結合一個畫筆CustomPainter
來實現繪製自定義圖形。接下來我將簡單介紹下圓形進度條的實現。
咱們先來看看CustomPaint
構造函數:
const CustomPaint({ Key key, this.painter, this.foregroundPainter, this.size = Size.zero, this.isComplex = false, this.willChange = false, Widget child, })
child
爲null
時,表明默認繪製區域大小,若是有child
則忽略此參數,畫布尺寸則爲child
尺寸。若是有child
可是想指定畫布爲特定大小,可使用SizeBox
包裹CustomPaint
實現。Flutter
會應用一些緩存策略來減小重複渲染的開銷。isComplex
配合使用,當啓用緩存時,該屬性表明在下一幀中繪製是否會改變。能夠看到,繪製時咱們須要提供前景或背景畫筆,二者也能夠同時提供。咱們的畫筆須要繼承CustomPainter
類,咱們在畫筆類中實現真正的繪製邏輯。
接下來,咱們看下怎麼經過CustomPainter
繪製圓形進度條:
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
中定義了一個虛函數paint
:
void paint(Canvas canvas, Size size);
這個函數是繪製的核心所在,它包含了如下兩個參數:
drawLine(畫線)
、drawRect(畫矩形)
、drawCircle(畫圓)
等畫布如今有了,那麼接下來咱們就須要一支畫筆了。Flutter
提供了Paint
類來實現畫筆。並且能夠配置畫筆的各類屬性如粗細、顏色、樣式等,好比:
final paint = Paint() ..color = color // 顏色 ..strokeWidth = width // 寬度 ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round;
最後,咱們就是須要使用drawArc
方法進行圓弧的繪製了:
canvas.drawArc( Offset.zero & size, -math.pi / 2, math.pi * 2 * value, false, paint, );
到此,咱們就完成了進度條的繪製。另外咱們也須要注意下繪製性能問題。好在類中提供了重寫shouldRepaint
的方法,這個方法決定了畫布何時會從新繪製,在複雜的繪製中對提高繪製性能是至關有成效的。
@override bool shouldRepaint(_CirclePainter oldDelegate) => value != oldDelegate.value;
毫無疑問,Flutter
的前景是一片光明的,也許如今還存在諸多問題,但我相信更多的人會願意陪着Flutter
一塊兒成長。期待着Flutter
的生態圈的完善。後期我也會逐步完善Flutter EasyLoading
,期待您的寶貴意見。
最後,但願Flutter EasyLoading
對您有所幫助。