用Flutter實現一個仿Twitter的點贊效果
這次依然是補作業,之前在寫仿“探探”左滑/右滑的效果的時候,設計稿底部的喜歡Icon其實是有類似於Twitter點贊那種的動效的,但是因為時間原因我偷懶沒寫。
慣例先上效果圖:
GitHub地址: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();
複製程式碼