基于Flutter實(shí)現(xiàn)風(fēng)車(chē)加載組件的制作
前言
Flutter 官方提供了諸如 CircularProgressIndicator
和 LinearProgressIndicator
兩種常見(jiàn)的加載指示組件,但是說(shuō)實(shí)話,實(shí)在太普通,比如下面這個(gè)CircularProgressIndicator
。
正好我們介紹到了動(dòng)畫(huà)環(huán)節(jié),那我們自己來(lái)一個(gè)有趣的加載指示組件吧。創(chuàng)意送哪來(lái)呢,冥思苦想中腦海里突然就響起了一首歌:
大風(fēng)車(chē)吱呀吱喲喲地轉(zhuǎn),這里的風(fēng)景呀真好看! 天好看,地好看
沒(méi)錯(cuò),這就是當(dāng)時(shí)風(fēng)靡全中國(guó)的放學(xué)檔,兒童必看節(jié)目《大風(fēng)車(chē)》的主題曲。
嗯,我們就自己來(lái)個(gè)風(fēng)車(chē)動(dòng)畫(huà)加載組件吧,最終完成效果如下,支持尺寸和旋轉(zhuǎn)速度的設(shè)定。
接口定義
遵循接口先行的習(xí)慣,我們先設(shè)計(jì)對(duì)外的接口。對(duì)于一個(gè)動(dòng)畫(huà)加載組件,我們需要支持兩個(gè)屬性:
- 尺寸:可以由調(diào)用者決定尺寸的大小,以便應(yīng)用在不同的場(chǎng)合。由于風(fēng)車(chē)加載是個(gè)正方形,我們定義參數(shù)名為
size
,類型為double
。 - 速度:風(fēng)車(chē)是旋轉(zhuǎn)的,需要支持旋轉(zhuǎn)速度調(diào)節(jié),以便滿足應(yīng)用用戶群體的偏好。我們定義參數(shù)名為
speed
,單位是轉(zhuǎn)/秒,即一秒旋轉(zhuǎn)多少圈,類型也是double
。 - 旋轉(zhuǎn)方向:可以控制順時(shí)針還是逆時(shí)針?lè)较蛐D(zhuǎn)。參數(shù)名為
direction
,為枚舉。枚舉名稱為RotationDirection
,有兩個(gè)枚舉值,分別是clockwise
和antiClockwise
。
其實(shí)還可以支持顏色設(shè)置,不過(guò)看了一下,大部分風(fēng)車(chē)是4個(gè)葉片,顏色為藍(lán)黃紅綠組合,這里我們就直接在組件內(nèi)部固定顏色了,這樣也可以簡(jiǎn)化調(diào)用者的使用。 然后是定義組件名稱,我們依據(jù)英文意思,將組件命名為 WindmillIndicator
。
實(shí)現(xiàn)思路
風(fēng)車(chē)?yán)L制
關(guān)鍵是繪制風(fēng)車(chē),根據(jù)給定的尺寸繪制完風(fēng)車(chē)后,再讓它按設(shè)定的速度旋轉(zhuǎn)起來(lái)就好了。繪制風(fēng)車(chē)的關(guān)鍵點(diǎn)又在于繪制葉片,繪制一個(gè)葉片后,其他三個(gè)葉片依次旋轉(zhuǎn)90度就可以了。我們來(lái)看一下葉片的繪制。葉片示意圖如下:
葉片整體在一個(gè)給定尺寸的正方形框內(nèi),由三條線組成:
- 紅色線:弧線,我們?cè)O(shè)定起點(diǎn)在底邊X 軸方向1/3寬度處,終點(diǎn)是左側(cè)邊 Y 軸方向1/3高度處,圓弧半徑為邊長(zhǎng)的一半。
- 綠色線:弧線,起點(diǎn)為紅色線的終點(diǎn),終點(diǎn)為右上角頂點(diǎn),圓弧半徑為邊長(zhǎng)。
- 藍(lán)色線,連接綠色線的終點(diǎn)和紅色線的起點(diǎn),以達(dá)到閉合。
有了葉片,其他的就是依次旋轉(zhuǎn)90度了,繪制完后的示意圖如下所示:
旋轉(zhuǎn)效果
我們把每一個(gè)葉片作為獨(dú)立的組件,按照設(shè)定的速度,更改旋轉(zhuǎn)角度即可,只要4個(gè)葉片的旋轉(zhuǎn)增量角度同時(shí)保持一致,風(fēng)車(chē)的形狀就能夠一致保持,這樣就有風(fēng)車(chē)旋轉(zhuǎn)的效果了。
代碼實(shí)現(xiàn)
WindmillIndicator
定義
WindmillIndicator
需要使用 Animation
和 AnimationController
來(lái)控制動(dòng)畫(huà),因此是一個(gè) StatefulWidget
。根據(jù)我們上面的接口定義,得到WindmillIndicator
的定義如下:
class WindmillIndicator extends StatefulWidget { final size; // 旋轉(zhuǎn)速度,默認(rèn):1轉(zhuǎn)/秒 final double speed; final direction; WindmillIndicator({Key? key, this.size = 50.0, this.speed = 1.0, this.direction = RotationDirection.clockwise, }) : assert(speed > 0), assert(size > 0), super(key: key); @override _WindmillIndicatorState createState() => _WindmillIndicatorState(); }
這里使用了 assert
來(lái)防止參數(shù)錯(cuò)誤,比如 speed
不能是負(fù)數(shù)和0(因?yàn)楹竺嬗?jì)算旋轉(zhuǎn)速度需要將 speed
當(dāng)除數(shù)來(lái)計(jì)算動(dòng)畫(huà)周期),同時(shí) size
不可以小于0。
旋轉(zhuǎn)速度設(shè)定
我們使用 Tween<double>
設(shè)定Animation
的值的范圍,begin
和 end
為0和1.0,然后每個(gè)葉片在構(gòu)建的時(shí)候旋轉(zhuǎn)角度都加上2π 弧度乘以 Animation
對(duì)象的值,這樣一個(gè)周期下來(lái)就是旋轉(zhuǎn)了一圈。然后是 AnimationController
來(lái)控制具體的選擇速度,實(shí)際的時(shí)間使用毫秒數(shù),用1000 / speed
得到的就是旋轉(zhuǎn)一圈需要的毫秒數(shù)。這樣即能夠設(shè)定旋轉(zhuǎn)速度為 speed
。代碼如下所示:
class _WindmillIndicatorState extends State<WindmillIndicator> with SingleTickerProviderStateMixin { late Animation<double> animation; late AnimationController controller; @override void initState() { super.initState(); int milliseconds = 1000 ~/ widget.speed; controller = AnimationController( duration: Duration(milliseconds: milliseconds), vsync: this); animation = Tween<double>(begin: 0, end: 1.0).animate(controller) ..addListener(() { setState(() {}); }); controller.repeat(); } @override Widget build(BuildContext context) { return AnimatedWindmill( animation: animation, size: widget.size, direction: widget.direction, ); } @override void dispose() { if (controller.status != AnimationStatus.completed && controller.status != AnimationStatus.dismissed) { controller.stop(); } controller.dispose(); super.dispose(); }
這里在initState 里設(shè)置好參數(shù)之后就調(diào)用了controller.repeat()
,以使得動(dòng)畫(huà)重復(fù)進(jìn)行。在 build 方法里,我們構(gòu)建了一個(gè)AnimatedWindmill
對(duì)象,將 Animation
對(duì)象和 size
傳給了它。AnimatedWindmill
是風(fēng)車(chē)的繪制和動(dòng)畫(huà)組件承載類。
風(fēng)車(chē)葉片繪制
風(fēng)車(chē)葉片代碼定義如下:
class WindmillWing extends StatelessWidget { final double size; final Color color; final double angle; const WindmillWing( {Key? key, required this.size, required this.color, required this.angle}); @override Widget build(BuildContext context) { return Container( transformAlignment: Alignment.bottomCenter, transform: Matrix4.translationValues(0, -size / 2, 0)..rotateZ(angle), child: ClipPath( child: Container( width: size, height: size, alignment: Alignment.center, color: color, ), clipper: WindwillClipPath(), ), ); } }
共接收三個(gè)參數(shù):
- size:即矩形框的邊長(zhǎng);
- color:葉片填充顏色;
- angle:葉片旋轉(zhuǎn)角度。
實(shí)際葉片旋轉(zhuǎn)時(shí)參照底部中心位置(bottomCenter
)旋轉(zhuǎn)(不同位置的效果不一樣,感興趣的可以拉取代碼修改試試)。這里有兩個(gè)額外的注意點(diǎn):
transform
參數(shù)我們首先往 Y 軸做了 size / 2
的平移,這是因?yàn)樾D(zhuǎn)后風(fēng)車(chē)整體位置會(huì)偏下size / 2
,因此上移補(bǔ)償,保證風(fēng)車(chē)的位置在中心。
實(shí)際葉片的形狀是對(duì) Container
進(jìn)行裁剪得來(lái)的,這里使用了 ClipPath
類。ClipPath
支持使用自定義的CustomClipper<Path>
裁剪類最子元素的邊界進(jìn)行裁剪。我們定義了WindwillClipPath
類來(lái)實(shí)現(xiàn)我們說(shuō)的風(fēng)車(chē)葉片外觀裁剪,也就是把正方形裁剪為風(fēng)車(chē)葉片形狀。WindwillClipPath
的代碼如下,在重載的 getClip
方法中將我們所說(shuō)的葉片繪制路徑返回即可。
class WindwillClipPath extends CustomClipper<Path> { @override Path getClip(Size size) { var path = Path() ..moveTo(size.width / 3, size.height) ..arcToPoint( Offset(0, size.height * 2 / 3), radius: Radius.circular(size.width / 2), ) ..arcToPoint( Offset(size.width, 0), radius: Radius.circular(size.width), ) ..lineTo(size.width / 3, size.height); return path; } @override bool shouldReclip(covariant CustomClipper<Path> oldClipper) { return false; } }
風(fēng)車(chē)組件
有了風(fēng)車(chē)葉片組件,風(fēng)車(chē)組件構(gòu)建就簡(jiǎn)單多了(這也是拆分子組件的好處之一)。我們將風(fēng)車(chē)組件繼承 AnimatedWidget
,然后使用 Stack
組件將4個(gè)葉片組合起來(lái),每個(gè)葉片給定不同的顏色和旋轉(zhuǎn)角度即可。而旋轉(zhuǎn)角度是由葉片的初始角度加上Animation
對(duì)象控制的旋轉(zhuǎn)角度共同確定的。然后控制順時(shí)針還是逆時(shí)針根據(jù)枚舉值控制角度是增加還是減少就可以了,風(fēng)車(chē)組件的代碼如下:
class AnimatedWindmill extends AnimatedWidget { final size; final direction; AnimatedWindmill( {Key? key, required Animation<double> animation, required this.direction, this.size = 50.0, }) : super(key: key, listenable: animation); @override Widget build(BuildContext context) { final animation = listenable as Animation<double>; final rotationAngle = direction == RotationDirection.clockwise ? 2 * pi * animation.value : -2 * pi * animation.value; return Stack( alignment: Alignment.topCenter, children: [ WindmillWing( size: size, color: Colors.blue, angle: 0 + rotationAngle, ), WindmillWing( size: size, color: Colors.yellow, angle: pi / 2 + rotationAngle, ), WindmillWing( size: size, color: Colors.green, angle: pi + rotationAngle, ), WindmillWing( size: size, color: Colors.red, angle: -pi / 2 + rotationAngle, ), ], ); } }
運(yùn)行效果
我們分別看運(yùn)行速度為0.5和1的效果,實(shí)測(cè)感覺(jué)速度太快或太慢體驗(yàn)都一般,比較舒適的速度在0.3-0.8之間,當(dāng)然你想晃暈用戶的可以更快些。
源碼已提交至:動(dòng)畫(huà)相關(guān)源碼,想用在項(xiàng)目的可以直接把WindmillIndicator
的實(shí)現(xiàn)源文件windmill_indicator.dart
拷貝到自己的項(xiàng)目里使用。
總結(jié)
本篇實(shí)現(xiàn)了風(fēng)車(chē)旋轉(zhuǎn)的加載指示動(dòng)畫(huà)效果,通過(guò)這樣的效果可以提升用戶體驗(yàn),尤其是兒童類的應(yīng)用,絕對(duì)是體驗(yàn)加分的動(dòng)效。從 Flutter學(xué)習(xí)方面來(lái)說(shuō),重點(diǎn)是三個(gè)知識(shí):
Animation
、AnimationController
和 AnimatedWidget
的應(yīng)用;
Matrix4
控制Container
的平移和旋轉(zhuǎn)的使用;
使用 ClipPath
和自定義CustomClipper<Path>
對(duì)組件形狀進(jìn)行裁剪,這個(gè)在很多場(chǎng)景會(huì)用到,比如那些特殊形狀的組件。
以上就是基于Flutter實(shí)現(xiàn)風(fēng)車(chē)加載組件的制作的詳細(xì)內(nèi)容,更多關(guān)于Flutter風(fēng)車(chē)加載組件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
android studio的Handler簡(jiǎn)單實(shí)例代碼
今天通過(guò)實(shí)例代碼給大家介紹android studio的Handler簡(jiǎn)單用法,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-10-10Android Rreact Native 常見(jiàn)錯(cuò)誤總結(jié)
這篇文章主要介紹了Android Rreact Native 常見(jiàn)錯(cuò)誤總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-06-06Android開(kāi)發(fā)之關(guān)閉和打開(kāi)Speaker(揚(yáng)聲器)的方法
這篇文章主要介紹了Android開(kāi)發(fā)之關(guān)閉和打開(kāi)Speaker(揚(yáng)聲器)的方法,結(jié)合實(shí)例形式簡(jiǎn)單分析了Android揚(yáng)聲器的操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-03-03Android中CountDownTimer 實(shí)現(xiàn)倒計(jì)時(shí)功能
本篇文章主要介紹了Android中CountDownTimer 實(shí)現(xiàn)倒計(jì)時(shí)功能,CountDownTimer 是android 自帶的一個(gè)倒計(jì)時(shí)類,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05android計(jì)算器實(shí)現(xiàn)兩位數(shù)的加減乘除
這篇文章主要為大家詳細(xì)介紹了android計(jì)算器實(shí)現(xiàn)兩位數(shù)的加減乘除,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03Android動(dòng)畫(huà)之漸變動(dòng)畫(huà)(Tween Animation)詳解 (漸變、縮放、位移、旋轉(zhuǎn))
這篇文章主要介紹了Android動(dòng)畫(huà)之漸變動(dòng)畫(huà)(Tween Animation)用法,結(jié)合實(shí)例形式詳細(xì)分析了Android漸變動(dòng)畫(huà)Tween Animation實(shí)現(xiàn)漸變,縮放,位移,旋轉(zhuǎn)等技巧,需要的朋友可以參考下2016-01-01Android 用Time和Calendar獲取系統(tǒng)當(dāng)前時(shí)間源碼分享(年月日時(shí)分秒周幾)
這篇文章主要介紹了Android 用Time和Calendar獲取系統(tǒng)當(dāng)前時(shí)間源碼分享,包括年月日時(shí)分秒周幾的源碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下2017-01-01