Flutter仿掘金點贊效果

老孟Flutter發表於2020-05-20

老孟導讀:今天分享一下如何實現掘金點贊效果,這不僅僅是一篇技術文章,還是一篇解決問題思路的文章,遇到一個需求時,如何拆分需求,然後一步一步實現,這個過程比單純的技術(此文)更有含金量。

先來看一下掘金點讚的效果:

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個控制元件用法):laomengit.com

歡迎加入Flutter交流群(微信:laomengit)、關注公眾號【老孟Flutter】:

相關文章