这次依然是补作业,之前在写仿“探探”左滑/右滑的效果的时候,设计稿底部的喜欢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();