老孟导读:今天分享一下如何实现掘金点赞效果,这不单单是一篇技术文章,仍是一篇解决问题思路的文章,遇到一个需求时,如何拆分需求,而后一步一步实现,这个过程比单纯的技术(此文)更有含金量。
先来看一下掘金点赞的效果:html
说点题外话,感谢一下二哥(沉默王二 ),给了我不少建议和帮助,公众号搜索沉默王二便可关注。git
遇到组合动画效果时,首先拆分一下这个动画,以掘金点赞效果为例,共分为3个动画效果:微信
拆分好了以后,就一步一步实现其效果。app
小手缩放效果须要2个图标,选中和未选中两种状态,我从阿里的图标库中选了2个相似的图标(未找到一摸同样的)。两种状态的图标定义以下:ide
/// /// 未点赞icon /// const Icon _unLikeIcon = Icon( IconData(0xe60a, fontFamily: 'appIconFonts'), ); /// /// 点赞icon /// const Icon _likeIcon = Icon( IconData(0xe60c, fontFamily: 'appIconFonts'), color: Color(0xFF1afa29), );
关于如何使用阿里的图标库中的图标能够查看此文章。布局
因为小手图标的动画效果是放大->还原,使用组合动画实现其效果,代码以下:动画
@override initState() { _animationController = AnimationController(duration: Duration(milliseconds: 300), vsync: this); _iconAnimation = Tween(begin: 1.0, end: 1.3).animate(_animationController); _iconAnimation = TweenSequence([ TweenSequenceItem( tween: Tween(begin: 1.0, end: 1.3) .chain(CurveTween(curve: Curves.easeIn)), weight: 50), TweenSequenceItem(tween: Tween(begin: 1.3, end: 1.0), weight: 50), ]).animate(_animationController); } @override Widget build(BuildContext context) { return _buildLikeIcon(); } _buildLikeIcon() { return ScaleTransition( scale: _iconAnimation, child: widget.like ? IconButton( padding: EdgeInsets.all(0), icon: _likeIcon, onPressed: () { _clickIcon(); }, ) : IconButton( padding: EdgeInsets.all(0), icon: _unLikeIcon, onPressed: () { _clickIcon(); }, ), ); }
添加按钮点击效果:ui
_clickIcon() { if (_iconAnimation.status == AnimationStatus.forward || _iconAnimation.status == AnimationStatus.reverse) { return; } setState(() { widget.like = !widget.like; }); if (_iconAnimation.status == AnimationStatus.dismissed) { _animationController.forward(); } else if (_iconAnimation.status == AnimationStatus.completed) { _animationController.reverse(); } }
圆环的动画效果是线条宽度逐渐变为0,透明度逐渐变为0,相对简单,使用AnimatedBuilder实现:this
_buildCircle() { return !widget.like ? Container() : AnimatedBuilder( animation: _circleAnimation, builder: (BuildContext context, Widget child) { return Container( decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: Color(0xFF5FA0EC) .withOpacity(_circleAnimation.value), width: _circleAnimation.value * 8)), ); }, ); }
定义_circleAnimation:spa
_circleAnimation = Tween(begin: 1.0, end: 0.0).animate(_animationController);
最外圈的小点点动画效果是最简单的,透明度逐渐变为0,但布局相对复杂,围绕小手造成一个圆形,使用Flow实现此布局,Flow是一个很是酷炫的布局组件,更多用法查看此文。
构建单个小圆点
_buildCirclePoint(double radius, Color color) { return !widget.like ? Container() : AnimatedBuilder( animation: _circleAnimation, builder: (BuildContext context, Widget child) { return Container( width: radius, height: radius, decoration: BoxDecoration( shape: BoxShape.circle, color: color.withOpacity(_circleAnimation.value)), ); }, ); }
构建围绕小手的多个点:
_buildCirclePoints() { return Flow( delegate: CirclePointFlowDelegate(), children: <Widget>[ _buildCirclePoint(2, Color(0xFF97B1CE)), _buildCirclePoint(5, Color(0xFF4AC6B7)), _buildCirclePoint(2, Color(0xFF97B1CE)), _buildCirclePoint(5, Color(0xFF4AC6B7)), _buildCirclePoint(2, Color(0xFF97B1CE)), _buildCirclePoint(5, Color(0xFF4AC6B7)), _buildCirclePoint(2, Color(0xFF97B1CE)), _buildCirclePoint(5, Color(0xFF4AC6B7)), _buildCirclePoint(2, Color(0xFF97B1CE)), _buildCirclePoint(5, Color(0xFF4AC6B7)), _buildCirclePoint(2, Color(0xFF97B1CE)), _buildCirclePoint(5, Color(0xFF4AC6B7)), _buildCirclePoint(2, Color(0xFF97B1CE)), _buildCirclePoint(5, Color(0xFF4AC6B7)), _buildCirclePoint(2, Color(0xFF97B1CE)), _buildCirclePoint(5, Color(0xFF4AC6B7)), ], ); }
CirclePointFlowDelegate 定义以下:
class CirclePointFlowDelegate extends FlowDelegate { CirclePointFlowDelegate(); @override void paintChildren(FlowPaintingContext context) { var radius = min(context.size.width, context.size.height) / 2.0; //中心点 double rx = radius; double ry = radius; for (int i = 0; i < context.childCount; i++) { if (i % 2 == 0) { double x = rx + (radius - 5) * cos(i * 2 * pi / (context.childCount - 1)); double y = ry + (radius - 5) * sin(i * 2 * pi / (context.childCount - 1)); context.paintChild(i, transform: Matrix4.translationValues(x, y, 0)); } else { double x = rx + (radius - 5) * cos((i - 1) * 2 * pi / (context.childCount - 1) + 2 * pi / ((context.childCount - 1) * 3)); double y = ry + (radius - 5) * sin((i - 1) * 2 * pi / (context.childCount - 1) + 2 * pi / ((context.childCount - 1) * 3)); context.paintChild(i, transform: Matrix4.translationValues(x, y, 0)); } } } @override bool shouldRepaint(FlowDelegate oldDelegate) => true; }
老孟Flutter博客地址(近200个控件用法):http://laomengit.com
欢迎加入Flutter交流群(微信:laomengit)、关注公众号【老孟Flutter】:
![]() |
![]() |