Flutter Matrix4矩陣動畫實(shí)現(xiàn)移動、縮放、旋轉(zhuǎn),讓你的紙飛機(jī)沿著貝塞爾曲線軌跡飛起來

image

用到的知識點(diǎn)

  • Matrix4矩陣
  • 貝塞爾曲線

第一步:畫出目標(biāo)運(yùn)行大致軌跡路線

首先我們先畫一條二階貝塞爾曲線,這樣我們能更直觀的觀察到目標(biāo)移動的大致軌跡。我們先確定二階貝塞爾曲線的三個點(diǎn):p0(開始點(diǎn))、p1(控制點(diǎn))、p2(結(jié)束點(diǎn)),像上圖的紅線軌跡,屏幕的中心點(diǎn)開始,到達(dá)屏幕的寬2/3的位置,那么p0和p2的點(diǎn)就可以確定了:

// 二階貝塞爾曲線 p0:開始點(diǎn)、p1:控制點(diǎn)、p2:結(jié)束點(diǎn)
Offset p0, p1, p2;

@override
Widget build(BuildContext context) {
  Size _size = MediaQuery.of(context).size;
  if (p0 == null) {
    p0 = Offset(_size.width/2, _size.height/2);
    p1 = Offset(_size.width+_size.width/4, _size.height/4);
    p2 = Offset(_size.width*2/3, 0);
  }
}
復(fù)制代碼

得到二階貝塞爾曲線的三個點(diǎn)之后就可以用 CustomPaint 把它畫出來了,因?yàn)槊看?setState 之后,build 會重新走一遍,所以我加了判斷讓它們只初始化一次就夠了。

第二步:放置紙飛機(jī)在屏幕的中心點(diǎn)

Flutter里面使用矩陣是通過 Transform 這個 Widget 來設(shè)置的,Transform 有多個擴(kuò)展構(gòu)造方法,比如 Transform.rotate、Transform.translate、Transform.scale,有興趣的可以自己去了解一下,在這里我們使用 Transform 默認(rèn)的構(gòu)造方法才能通過 Matrix4 來實(shí)現(xiàn)飛機(jī)的移動、縮放、旋轉(zhuǎn)。

// 矩陣
Matrix4 _matrix4 = Matrix4.identity();
// 飛機(jī)尺寸
Size planeSize;

@override
Widget build(BuildContext context) {
  Size _size = MediaQuery.of(context).size;
  if (planeSize == null) {
    planeSize = Size(_size.width/4, _size.width/4);
  }

  return Stack(
    children: [
      Container(
        alignment: Alignment.center,
        child: Transform(
          transform: _matrix4,
          child: SvgPicture.asset(Res.svg_paper_plane, width: planeSize.width, height: planeSize.height,),
        ),
      ),
    ],
  );
}
復(fù)制代碼

第三步:設(shè)置動畫,通過動畫計(jì)算飛機(jī)移動軌跡等

這一步最重要,涉及到移動軌跡、旋轉(zhuǎn)角度和縮放倍數(shù)的計(jì)算,這里面涉及到一些計(jì)算,我直接把代碼貼出來(PS:畢竟我數(shù)學(xué)太過垃圾,這一步花了我好多時間也沒能找到移動距離、縮放倍數(shù)、旋轉(zhuǎn)角度的最佳計(jì)算公式,只能自己一步一步慢慢調(diào)到大致的視覺效果)。

// 移動軌跡點(diǎn),即移動物的中心點(diǎn)
Offset bezierCenter;
// 當(dāng)前移動距離
Offset transSize = Offset(0.0, 0.0);

/// 初始化動畫
_initAnim() {
  _animationController = AnimationController(duration: Duration(seconds: 3), vsync: this);
  _animation = Tween(begin: 0.0, end: 1.0).animate(_animationController);
  _animation.addListener(() {
    // t 動態(tài)變化的值
    var t = _animation.value;
    if (mounted) {
      setState(() {
        _matrix4 = Matrix4.identity();
        // 根據(jù)二階貝塞爾曲線計(jì)算移動軌跡點(diǎn)
        double _left = pow(1 - t, 2) * p0.dx + 2 * t * (1 - t) * p1.dx + pow(t, 2) * p2.dx;
        double _top = pow(1 - t, 2) * p0.dy + 2 * t * (1 - t) * p1.dy + pow(t, 2) * p2.dy;
        // 設(shè)置移動
        if (bezierCenter == null) {
          transSize = Offset(0.0, 0.0);
        } else {
          transSize = Offset(transSize.dx - (bezierCenter.dx - _left),
              transSize.dy - (bezierCenter.dy - _top));
        }
        _matrix4..translate(transSize.dx, transSize.dy, 0.0);
        bezierCenter = Offset(_left, _top);
        // 設(shè)置縮小倍數(shù)
        _matrix4..scale((1-t) < 0.4 ? 0.4 : (1-t));
        // 設(shè)置旋轉(zhuǎn)角度
        double rotate = pi/2*t;
        _matrix4..rotateX(rotate > rotate*0.8 ? rotate*0.8 : rotate);
        _matrix4..rotateY(rotate > rotate*0.8 ? -rotate*0.8 : -rotate);
        _matrix4..rotateZ(rotate > rotate*0.8 ? -rotate*0.8 : -rotate);
      });
    }
  });

}
復(fù)制代碼

最后附錄上完整代碼(Github):PlaneFly

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容