用Flutter實現一個仿Twitter的點贊效果
這次依然是補作業,之前在寫仿“探探”左滑/右滑的效果的時候,設計稿底部的喜歡Icon其實是有類似於Twitter點贊那種的動效的,但是因為時間原因我偷懶沒寫。
慣例先上效果圖:

GitHub地址: FlutterUI%2Ftree%2Fmaster%2Flib%2Flikebutton" rel="nofollow,noindex">github.com/yumi0629/Fl…
整體演算法是參照了GitHub上star最多的jd-alexander大佬寫的LikeButton,我進行了調整,並最終用Flutter實現。
一點小缺陷:現在的實現方式,icon大小沒法自適應,需要初始化佈局的時候手動傳入一個size。
設計思路
我們將動畫放慢,很明顯整體動畫由三部分組成:中間Icon的放大、底部圓環的交替和外部煙花散開的效果:

整體佈局
因為是層疊佈局,我們依舊是使用Stack來實現,底部圓環的交替和外部煙花散開的效果是使用的 CustomPaint
來繪製,而中間的小圖示則是使用的普通Widget實現:
Stack( alignment: Alignment.center, children: <Widget>[ CustomPaint( size: Size(widget.width, widget.width), painter: DotPainter(), ), CustomPaint( isComplex: true, size: Size(widget.width * 0.35, widget.width * 0.35), painter: CirclePainter(), Container( width: widget.width, height: widget.width, alignment: Alignment.center, child: Transform.scale( scale: isLiked ? scale.value : 1.0, child: GestureDetector( child: Icon(), onTap: _onTap, ), ), ), ], ); 複製程式碼
Paint的繪製在這裡就不多說了,因為基本都是數學問題。整體效果的實現主要是要學會用一個Controller來控制多個動畫。
動畫控制 Staggered Animation
這一期主要是想跟大家講講如何用一個Controller來控制多個動畫同時進行,也就是Flutter中的 Staggered Animation (交錯動畫)。
我們可以定義很多個Animation,將他們和同一個controller繫結:
Animation<double> outerCircle = new Tween<double>( begin: 0.1, end: 1.0, ).animate( new CurvedAnimation( parent: _controller, curve: new Interval( 0.0, 0.3, curve: Curves.ease, ), ), ); Animation<double> innerCircle = new Tween<double>( begin: 0.2, end: 1.0, ).animate( new CurvedAnimation( parent: _controller, curve: new Interval( 0.2, 0.5, curve: Curves.ease, ), ), );Animation<double> 複製程式碼
上面的例子中, outerCircle
和 innerCircle
共享同一個 _controller
,而各自的播放順序通過 Interval
來控制: outerCircle
的動畫時間為整體進度的0.0~0.3, innerCircle
的動畫時間為整體進度的0.2~0.5。對於單個動畫的進度,我們可以通過 outerCircle.value
和 innerCircle.value
來獲取,單個動畫的進度範圍依然是0.0~1.0,所以繪製每一組動畫時,不需要去手動轉換。
繪製中的一些坑
這次碰到的主要問題是 paint
的 blendMode
屬性有坑,因為底部兩個圓環在繪製時的思路是:大圓環繪製到一半時,開始繪製小圓環將其遮蓋住,但是小圓環的顏色我們沒法設定,因為我們不知道畫布是什麼顏色的,因此沒法用相同的顏色去遮蓋住大圓環。這個問題可以通過設定 paint
的 blendMode
屬性為 BlendMode.clear
解決,看名字就很好理解,就是清除之前的繪製區域,但是這個 BlendMode.clear
有bug。
如果你直接像下面這樣寫,那麼你會發現,clear掉的部分會變成黑色:
circlePaint..style = PaintingStyle.fill; maskPaint..blendMode = BlendMode.clear; @override void paint(Canvas canvas, Size size) { canvas.drawCircle(Offset(center, center), 20.0, circlePaint); canvas.drawCircle(Offset(center, center), 10.0, maskPaint); } 複製程式碼

解決方法就是在繪製前儲存一下當前layer:
canvas.saveLayer(Offset.zero & size, Paint()); canvas.drawCircle(Offset(center, center), 20.0, circlePaint); canvas.drawCircle(Offset(center, center), 10.0, maskPaint); canvas.restore(); 複製程式碼