Flutter仿掘金点赞效果
老孟导读:今天分享一下如何实现掘金点赞效果,这不仅仅是一篇技术文章,还是一篇解决问题思路的文章,遇到一个需求时,如何拆分需求,然后一步一步实现,这个过程比单纯的技术(此文)更有含金量。
先来看一下掘金点赞的效果:
说点题外话,感谢一下二哥(沉默王二 ),给了我很多建议和帮助,公众号搜索沉默王二即可关注。
遇到组合动画效果时,首先拆分一下这个动画,以掘金点赞效果为例,共分为3个动画效果:
- 小手图标改变颜色并且缩放一下。
- 圆环由粗变细,透明度逐渐变为0。
- 最外圈小点点透明度逐渐变为0。
拆分好了之后,就一步一步实现其效果。
小手缩放效果
小手缩放效果需要2个图标,选中和未选中两种状态,我从阿里的图标库中选了2个类似的图标(未找到一摸一样的)。两种状态的图标定义如下:
///
/// 未点赞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();
},
),
);
}
添加按钮点击效果:
_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实现:
_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:
_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个控件用法)://laomengit.com
欢迎加入Flutter交流群(微信:laomengit)、关注公众号【老孟Flutter】: