?flutter_easyloading: 一個(gè)簡單易用的Flutter插件,包含23種loading動畫效果仇冯、進(jìn)度條展示进萄、Toast展示。純Flutter端實(shí)現(xiàn)磁玉,支持iOS停忿、Android。
前言
Flutter
是Google
在2017年推出的一套開源跨平臺UI
框架蚊伞,可以快速地在iOS
席赂、Android
和Web
平臺上構(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ù)覽:
實(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)建StatelessWidget
的build
方法 -
State
對象中:創(chuàng)建StatefulWidget
的State
對象的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
插件这难,包含23種loading
動畫效果舟误、進(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)過程及思路:
-
Overlay
、OverlayEntry
實(shí)現(xiàn)全局彈窗 -
CustomPaint
與Canvas
實(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è)Stack
的Widget
士修,可以將OverlayEntry
插入到Overlay
中,使獨(dú)立的child
窗口懸浮于其他Widget
之上樱衷。利用這個(gè)特性,我們可以用Overlay
將 MaterialApp
或CupertinoApp
包裹起來酒唉,這樣做的目的是為了確保 loading
組件能覆蓋在其他組件之上矩桂,因?yàn)樵?code>Flutter中只會存在一個(gè)MaterialApp
或CupertinoApp
根節(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)很多我們想要的效果钉凌。
CustomPaint
與Canvas
實(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)
child
為null
時(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
對您有所幫助芍瑞。