Flutter动画入门—— 内外逆向环Loading动画

1,968 阅读4分钟

系列文章

Flutter - 仿Airbnb的价格区间筛选器。(一)

仿网易云音乐APP

仿同花顺自选股列表

仿高德三级联动Drawer

效果图

标题有些拗口,一开始的标题好像触发敏感词了,所以只能这样了

分析

很明显这是由两种颜色组成滴~

运动轨迹分别是里面一个圈、外面一个圈。具体表现为,外层两个四分之一弧,内层两个四分之一弧,且中心对称。
内外相对运动,内圈速度要略慢一些,毕竟周长比较短(速度全凭个人喜好)~

按内外分为两个widget,并对他们进行旋转,就可以实现了。

弧度表

来源网络,这个下面会用到

绘制代码

绘制中心对称的两个90度弧,使用CustomPaint即可实现。
以外部widget(黑色)为例,代码如下:
CustomPaint(
    painter: OuterPainter(),///传入我们自己的 painter
)


class OuterPainter extends CustomPainter{
    @override
    void paint(Canvas canvas, Size size) {
        ///canvas 是我们作画的画布,Size则是父widget给我们的画布大小
        ///也可以通过CustomPaint的size属性指定,
        ///其次如果CustomPaint的child不为空,这个size的值会与child大小有关
        ///CustomPaint的size将被忽略
        ///初始化一个画笔
        Paint paint = Paint();
        paint.color = Color.fromRGBO(51, 51, 51, 1);
        paint.strokeWidth = 6;///画笔粗细
        paint.isAntiAlias = true;///抗锯齿
        paint.style = PaintingStyle.stroke;///默认是fill,我们不需要填充,选stroke 
        ///首先我们需要一个矩形,画布好根据这个矩形来确定圆的位置(这个圆是抽象的)
        Rect rect = Rect.fromCircle(center: Offset(size.width/2,size.height/2),radius: size.width/2);
        /// drawArc 绘制一条弧线
        /// 参数1 确定圆的矩形,
        /// 参数2和3,分别是起始位置和扫过的角度,它原名是叫startAngle 和 sweepAngle,
        /// 0.0从中心点到右侧0度 , 扫过pi/2 度 (90度),
        /// 参数4(userCenter)false 只绘制一个弧线、如果是true,则会绘制一个扇形
        /// 参数5 画笔
        canvas.drawArc(rect, 0.0, pi/2, false, paint);
        ///因为是中心对称,所以我们将位置移动180度
        canvas.drawArc(rect, pi, pi/2, false, paint);


}

    @override
    bool shouldRepaint(CustomPainter oldDelegate) {
        return false; ///如果你的painter和一个动画绑定,理论上这里应是true(或者根据自己需要的值进行判断)表示需要重绘
    }

}


这样我们就可以绘制出外面的widget了,同理可以绘制出里面的,下面我们就开始让他们转了。

动画代码

我们可以直接使用 RotationTransition包裹我们的widget,并传入对应的animation,通过animationController来控制。首先我们先定义animation,代码如下:


AnimationController outerController,innerController;
Animation outerAnim,innerAnim;
  @override
void initState() {
super.initState();
///使用动画的页面 要混入TickerProviderStateMixin,否则 vsync传入参数this 会报错,
/// 简单的讲,每次vsync信号回来的回调,会计算并更新动画的值,在随后刷新widget 便会看到改变
///(前提你的widget有值与动画相关)
///参数2:动画持续时间
outerController = AnimationController(vsync: this,duration: Duration(milliseconds:3000));
innerController = AnimationController(vsync: this,duration: Duration(milliseconds: 2000));

///补间动画,begin和end的差可以理解为动画的距离,可以看到外部要比内部的值,不管是从时间还是距离都要大一些
///,如果一致,内部的旋转速度就会比外部的快了。
outerAnim = Tween(begin: 0.0,end: 2.0).animate(outerController);
innerAnim = Tween(begin: 1.0,end: 0.0).animate(innerController);
///分别为动画的执行状态,这个见名知意,不做赘述了。
innerController.addStatusListener((status) {
  if (status == AnimationStatus.completed) {
    print("completed");
    innerController.reset();
    innerController.forward();
  } else if (status == AnimationStatus.dismissed) {
    print("dismissed");
    innerController.forward();
  } else if (status == AnimationStatus.forward) {
    print("forward");
  } else if (status == AnimationStatus.reverse) {
    print("reverse");
  }
});
outerController.addStatusListener((status) {
  if (status == AnimationStatus.completed) {
    print("completed");
    outerController.reset();
    outerController.forward();
  } else if (status == AnimationStatus.dismissed) {
    print("dismissed");
    outerController.forward();
  } else if (status == AnimationStatus.forward) {
    print("forward");
  } else if (status == AnimationStatus.reverse) {
    print("reverse");
  }
});

}

这里我们就完成了animation的定义,之后我们将outerAnim,innerAnim;分别传给RotationTransition的turns参数即可。

完整代码

class DemoPageState extends State<DemoPage> with TickerProviderStateMixin {

  AnimationController outerController,innerController;
  Animation outerAnim,innerAnim;

  @override
  void dispose() {
    outerController?.dispose();
    innerController?.dispose();
    super.dispose();
  }

  @override
  void initState() {
    super.initState();
    outerController = AnimationController(vsync: this,duration: Duration(milliseconds:3000));
    innerController = AnimationController(vsync: this,duration: Duration(milliseconds: 2000));

    outerAnim = Tween(begin: 0.0,end: 2.0).animate(outerController);
    innerAnim = Tween(begin: 1.0,end: 0.0).animate(innerController);
    innerController.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        print("completed");
        innerController.reset();
        innerController.forward();
      } else if (status == AnimationStatus.dismissed) {
        print("dismissed");
        innerController.forward();
      } else if (status == AnimationStatus.forward) {
        print("forward");
      } else if (status == AnimationStatus.reverse) {
        print("reverse");
      }
    });
    outerController.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        print("completed");
        outerController.reset();
        outerController.forward();
      } else if (status == AnimationStatus.dismissed) {
        print("dismissed");
        outerController.forward();
      } else if (status == AnimationStatus.forward) {
        print("forward");
      } else if (status == AnimationStatus.reverse) {
        print("reverse");
      }
    });

  }


  @override
  Widget build(BuildContext context) {
    if(!outerController.isAnimating)outerController.forward();
    if(!innerController.isAnimating)innerController.forward();
    return Container(
      color: Colors.white,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[

          Stack(
            alignment: Alignment.center,
            children: <Widget>[
              RotationTransition(
                turns:outerAnim ,
                child: Container(
                  width: 100,
                  height: 100,
                  child: CustomPaint(
                    painter: OuterPainter(),
                  ),
                ),
              ),
              RotationTransition(
                turns: innerAnim,
                child: Container(
                  width: 86,
                  height: 86,
                  child: CustomPaint(
                    painter: InnerPainter(),
                  ),
                ),
              ),

            ],
          ),
        ],
      ),
    );
  }
}

DEMO

github.com/bladeofgod/…

其他文章

Flutter - 仿Airbnb的价格区间筛选器。(一)

仿网易云音乐APP

仿同花顺自选股列表

仿高德三级联动Drawer