網(wǎng)站首頁 編程語言 正文
前言
Flutter 官方提供了諸如 CircularProgressIndicator
和 LinearProgressIndicator
兩種常見的加載指示組件,但是說實話,實在太普通,比如下面這個CircularProgressIndicator
。
正好我們介紹到了動畫環(huán)節(jié),那我們自己來一個有趣的加載指示組件吧。創(chuàng)意送哪來呢,冥思苦想中腦海里突然就響起了一首歌:
大風車吱呀吱喲喲地轉(zhuǎn),這里的風景呀真好看! 天好看,地好看
沒錯,這就是當時風靡全中國的放學檔,兒童必看節(jié)目《大風車》的主題曲。
嗯,我們就自己來個風車動畫加載組件吧,最終完成效果如下,支持尺寸和旋轉(zhuǎn)速度的設定。
接口定義
遵循接口先行的習慣,我們先設計對外的接口。對于一個動畫加載組件,我們需要支持兩個屬性:
- 尺寸:可以由調(diào)用者決定尺寸的大小,以便應用在不同的場合。由于風車加載是個正方形,我們定義參數(shù)名為
size
,類型為double
。 - 速度:風車是旋轉(zhuǎn)的,需要支持旋轉(zhuǎn)速度調(diào)節(jié),以便滿足應用用戶群體的偏好。我們定義參數(shù)名為
speed
,單位是轉(zhuǎn)/秒,即一秒旋轉(zhuǎn)多少圈,類型也是double
。 - 旋轉(zhuǎn)方向:可以控制順時針還是逆時針方向旋轉(zhuǎn)。參數(shù)名為
direction
,為枚舉。枚舉名稱為RotationDirection
,有兩個枚舉值,分別是clockwise
和antiClockwise
。
其實還可以支持顏色設置,不過看了一下,大部分風車是4個葉片,顏色為藍黃紅綠組合,這里我們就直接在組件內(nèi)部固定顏色了,這樣也可以簡化調(diào)用者的使用。 然后是定義組件名稱,我們依據(jù)英文意思,將組件命名為 WindmillIndicator
。
實現(xiàn)思路
風車繪制
關鍵是繪制風車,根據(jù)給定的尺寸繪制完風車后,再讓它按設定的速度旋轉(zhuǎn)起來就好了。繪制風車的關鍵點又在于繪制葉片,繪制一個葉片后,其他三個葉片依次旋轉(zhuǎn)90度就可以了。我們來看一下葉片的繪制。葉片示意圖如下:
葉片整體在一個給定尺寸的正方形框內(nèi),由三條線組成:
- 紅色線:弧線,我們設定起點在底邊X 軸方向1/3寬度處,終點是左側(cè)邊 Y 軸方向1/3高度處,圓弧半徑為邊長的一半。
- 綠色線:弧線,起點為紅色線的終點,終點為右上角頂點,圓弧半徑為邊長。
- 藍色線,連接綠色線的終點和紅色線的起點,以達到閉合。
有了葉片,其他的就是依次旋轉(zhuǎn)90度了,繪制完后的示意圖如下所示:
旋轉(zhuǎn)效果
我們把每一個葉片作為獨立的組件,按照設定的速度,更改旋轉(zhuǎn)角度即可,只要4個葉片的旋轉(zhuǎn)增量角度同時保持一致,風車的形狀就能夠一致保持,這樣就有風車旋轉(zhuǎn)的效果了。
代碼實現(xiàn)
WindmillIndicator
定義
WindmillIndicator
需要使用 Animation
和 AnimationController
來控制動畫,因此是一個 StatefulWidget
。根據(jù)我們上面的接口定義,得到WindmillIndicator
的定義如下:
class WindmillIndicator extends StatefulWidget { final size; // 旋轉(zhuǎ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
來防止參數(shù)錯誤,比如 speed
不能是負數(shù)和0(因為后面計算旋轉(zhuǎn)速度需要將 speed
當除數(shù)來計算動畫周期),同時 size
不可以小于0。
旋轉(zhuǎn)速度設定
我們使用 Tween
設定Animation
的值的范圍,begin
和 end
為0和1.0,然后每個葉片在構建的時候旋轉(zhuǎn)角度都加上2π 弧度乘以 Animation
對象的值,這樣一個周期下來就是旋轉(zhuǎn)了一圈。然后是 AnimationController
來控制具體的選擇速度,實際的時間使用毫秒數(shù),用1000 / speed
得到的就是旋轉(zhuǎn)一圈需要的毫秒數(shù)。這樣即能夠設定旋轉(zhuǎn)速度為 speed
。代碼如下所示:
class _WindmillIndicatorState extends Statewith SingleTickerProviderStateMixin { late Animation animation; late AnimationController controller; @override void initState() { super.initState(); int milliseconds = 1000 ~/ widget.speed; controller = AnimationController( duration: Duration(milliseconds: milliseconds), vsync: this); animation = Tween (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ù)之后就調(diào)用了controller.repeat()
,以使得動畫重復進行。在 build 方法里,我們構建了一個AnimatedWindmill
對象,將 Animation
對象和 size
傳給了它。AnimatedWindmill
是風車的繪制和動畫組件承載類。
風車葉片繪制
風車葉片代碼定義如下:
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(), ), ); } }
共接收三個參數(shù):
- size:即矩形框的邊長;
- color:葉片填充顏色;
- angle:葉片旋轉(zhuǎn)角度。
實際葉片旋轉(zhuǎn)時參照底部中心位置(bottomCenter
)旋轉(zhuǎn)(不同位置的效果不一樣,感興趣的可以拉取代碼修改試試)。這里有兩個額外的注意點:
transform
參數(shù)我們首先往 Y 軸做了 size / 2
的平移,這是因為旋轉(zhuǎn)后風車整體位置會偏下size / 2
,因此上移補償,保證風車的位置在中心。
實際葉片的形狀是對 Container
進行裁剪得來的,這里使用了 ClipPath
類。ClipPath
支持使用自定義的CustomClipper
裁剪類最子元素的邊界進行裁剪。我們定義了WindwillClipPath
類來實現(xiàn)我們說的風車葉片外觀裁剪,也就是把正方形裁剪為風車葉片形狀。WindwillClipPath
的代碼如下,在重載的 getClip
方法中將我們所說的葉片繪制路徑返回即可。
class WindwillClipPath extends CustomClipper{ @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 oldClipper) { return false; } }
風車組件
有了風車葉片組件,風車組件構建就簡單多了(這也是拆分子組件的好處之一)。我們將風車組件繼承 AnimatedWidget
,然后使用 Stack
組件將4個葉片組合起來,每個葉片給定不同的顏色和旋轉(zhuǎn)角度即可。而旋轉(zhuǎn)角度是由葉片的初始角度加上Animation
對象控制的旋轉(zhuǎn)角度共同確定的。然后控制順時針還是逆時針根據(jù)枚舉值控制角度是增加還是減少就可以了,風車組件的代碼如下:
class AnimatedWindmill extends AnimatedWidget { final size; final direction; AnimatedWindmill( {Key? key, required Animationanimation, required this.direction, this.size = 50.0, }) : super(key: key, listenable: animation); @override Widget build(BuildContext context) { final animation = listenable as Animation ; 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, ), ], ); } }
運行效果
我們分別看運行速度為0.5和1的效果,實測感覺速度太快或太慢體驗都一般,比較舒適的速度在0.3-0.8之間,當然你想晃暈用戶的可以更快些。
源碼已提交至:動畫相關源碼,想用在項目的可以直接把WindmillIndicator
的實現(xiàn)源文件windmill_indicator.dart
拷貝到自己的項目里使用。
總結
本篇實現(xiàn)了風車旋轉(zhuǎn)的加載指示動畫效果,通過這樣的效果可以提升用戶體驗,尤其是兒童類的應用,絕對是體驗加分的動效。從 Flutter學習方面來說,重點是三個知識:
Animation
、AnimationController
和 AnimatedWidget
的應用;
Matrix4
控制Container
的平移和旋轉(zhuǎn)的使用;
使用 ClipPath
和自定義CustomClipper
對組件形狀進行裁剪,這個在很多場景會用到,比如那些特殊形狀的組件。
原文鏈接:https://juejin.cn/post/7017545460176912392
相關推薦
- 2022-05-02 go語言中值類型和指針類型的深入理解_Golang
- 2022-07-07 通過Golang編寫一個AES加密解密工具_Golang
- 2022-05-28 Python裝飾器詳細介紹_python
- 2022-11-14 Android開發(fā)RecyclerView單獨刷新使用技巧_Android
- 2022-10-16 pytorch中.numpy()、.item()、.cpu()、.detach()以及.data的使
- 2022-05-24 C#?JWT權限驗證的實現(xiàn)_C#教程
- 2023-10-16 forEach方法如何跳出循環(huán)和for方法跳出循環(huán)
- 2022-09-30 python實現(xiàn)圖像降噪_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結構-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支