用Flutter實現一個仿Twitter的點贊效果

吉原拉麵發表於2019-03-03

  這次依然是補作業,之前在寫仿“探探”左滑/右滑的效果的時候,設計稿底部的喜歡Icon其實是有類似於Twitter點贊那種的動效的,但是因為時間原因我偷懶沒寫。
  慣例先上效果圖:

用Flutter實現一個仿Twitter的點贊效果

  GitHub地址:github.com/yumi0629/Fl…

  整體演算法是參照了GitHub上star最多的jd-alexander大佬寫的LikeButton,我進行了調整,並最終用Flutter實現。
  一點小缺陷:現在的實現方式,icon大小沒法自適應,需要初始化佈局的時候手動傳入一個size。

設計思路

  我們將動畫放慢,很明顯整體動畫由三部分組成:中間Icon的放大、底部圓環的交替和外部煙花散開的效果:

用Flutter實現一個仿Twitter的點贊效果

整體佈局

  因為是層疊佈局,我們依舊是使用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> 

複製程式碼

  上面的例子中,outerCircleinnerCircle共享同一個_controller,而各自的播放順序通過Interval來控制:outerCircle的動畫時間為整體進度的0.0~0.3,innerCircle的動畫時間為整體進度的0.2~0.5。對於單個動畫的進度,我們可以通過outerCircle.valueinnerCircle.value來獲取,單個動畫的進度範圍依然是0.0~1.0,所以繪製每一組動畫時,不需要去手動轉換。

繪製中的一些坑

  這次碰到的主要問題是paintblendMode屬性有坑,因為底部兩個圓環在繪製時的思路是:大圓環繪製到一半時,開始繪製小圓環將其遮蓋住,但是小圓環的顏色我們沒法設定,因為我們不知道畫布是什麼顏色的,因此沒法用相同的顏色去遮蓋住大圓環。這個問題可以通過設定paintblendMode屬性為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);
  }
複製程式碼

用Flutter實現一個仿Twitter的點贊效果
  解決方法就是在繪製前儲存一下當前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();
複製程式碼

相關文章