Flutter仿掘金点赞效果

老孟导读:今天分享一下如何实现掘金点赞效果,这不仅仅是一篇技术文章,还是一篇解决问题思路的文章,遇到一个需求时,如何拆分需求,然后一步一步实现,这个过程比单纯的技术(此文)更有含金量。

先来看一下掘金点赞的效果:

jue_1

说点题外话,感谢一下二哥沉默王二 ),给了我很多建议和帮助,公众号搜索沉默王二即可关注。

遇到组合动画效果时,首先拆分一下这个动画,以掘金点赞效果为例,共分为3个动画效果:

  1. 小手图标改变颜色并且缩放一下。
  2. 圆环由粗变细,透明度逐渐变为0。
  3. 最外圈小点点透明度逐渐变为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();
  }
}
jue_2

圆环动画

圆环的动画效果是线条宽度逐渐变为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);
jue_3

最外圈小点点

最外圈的小点点动画效果是最简单的,透明度逐渐变为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;
}
jue_4

交流

老孟Flutter博客地址(近200个控件用法):http://laomengit.com

欢迎加入Flutter交流群(微信:laomengit)、关注公众号【老孟Flutter】:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355