欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Flutter實現(xiàn)牛頓擺動畫效果的示例代碼

 更新時間:2022年04月25日 08:46:01   作者:老李code  
牛頓擺大家應該都不陌生,也叫碰碰球、永動球(理論情況下),那么今天我們用Flutter實現(xiàn)這么一個理論中的永動球,可以作為加載Loading使用,需要的可以參考一下

前言

牛頓擺大家應該都不陌生,也叫碰碰球、永動球(理論情況下),那么今天我們用Flutter實現(xiàn)這么一個理論中的永動球,可以作為加載Loading使用。

- 知識點:繪制、動畫曲線、多動畫狀態(tài)更新

效果圖:

實現(xiàn)步驟

1、繪制靜態(tài)效果

首先我們需要把線和小圓球繪制出來,對于看過我之前文章的小伙伴來說這個就很簡單了,效果圖:

關鍵代碼:

// 小圓球半徑
double radius = 6;

/// 小球圓心和直線終點一致
//左邊小球圓心
Offset offset = Offset(20, 60);
//右邊小球圓心
Offset offset2 = Offset(20 * 6 * 8, 60);

Paint paint = Paint()
  ..color = Colors.black87
  ..strokeWidth = 2;

/// 繪制線
canvas.drawLine(Offset.zero, Offset(90, 0), paint);
canvas.drawLine(Offset(20, 0), offset, paint);
canvas.drawLine(
    Offset(20 + radius * 2, 0), Offset(20 + radius * 2, 60), paint);
canvas.drawLine(
    Offset(20 + radius * 4, 0), Offset(20 + radius * 4, 60), paint);
canvas.drawLine(
    Offset(20 + radius * 6, 0), Offset(20 + radius * 6, 60), paint);
canvas.drawLine(Offset(20 + radius * 8, 0), offset2, paint);

/// 繪制小圓球
canvas.drawCircle(offset, radius, paint);
canvas.drawCircle(Offset(20 + radius * 2, 60), radius, paint);
canvas.drawCircle(Offset(20 + radius * 4, 60), radius, paint);
canvas.drawCircle(Offset(20 + radius * 6, 60), radius, paint);
canvas.drawCircle(offset2, radius, paint);

2、加入動畫

思路: 我們可以看到5個小球一共2個小球在運動,左邊小球運動一個來回之后傳遞給右邊小球,右邊小球開始運動,右邊一個來回再傳遞給左邊開始,也就是左邊運動周期是:0-1-0,正向運動一次,反向再運動一次,這樣就是一個周期,右邊也是一樣,左邊運動完傳遞給右邊,右邊運動完傳遞給左邊,這樣就簡單實現(xiàn)了牛頓擺的效果。

兩個關鍵點

小球運動路徑: 小球的運動路徑是一個弧度,以豎線的起點為圓心,終點為半徑,那么我們只需要設置小球運動至最高點的角度即可,通過角度就可計算出小球的坐標點。

運動曲線: 當然我們知道牛頓擺小球的運動曲線并不是勻速的,他是有一個加速減速過程的,撞擊之后,小球先加速然后減速達到最高點速度為0,之后速度再從0慢慢加速進行撞擊小球,周而復始。

下面的運動曲線就是先加速再減速,大概符合牛頓擺的運動曲線。我們就使用這個曲線看看效果。

完整源碼

class OvalLoading extends StatefulWidget {
  const OvalLoading({Key? key}) : super(key: key);

  @override
  _OvalLoadingState createState() => _OvalLoadingState();
}

class _OvalLoadingState extends State<OvalLoading>
    with TickerProviderStateMixin {
  // 左邊小球
  late AnimationController _controller =
      AnimationController(vsync: this, duration: Duration(milliseconds: 300))
        ..addStatusListener((status) {
          if (status == AnimationStatus.completed) {
            _controller.reverse(); //反向執(zhí)行 1-0
          } else if (status == AnimationStatus.dismissed) {
            _controller2.forward();
          }
        })
        ..forward();
  // 右邊小球
  late AnimationController _controller2 =
      AnimationController(vsync: this, duration: Duration(milliseconds: 300))
        ..addStatusListener((status) {
          // dismissed 動畫在起始點停止
          // forward 動畫正在正向執(zhí)行
          // reverse 動畫正在反向執(zhí)行
          // completed 動畫在終點停止
          if (status == AnimationStatus.completed) {
            _controller2.reverse(); //反向執(zhí)行 1-0
          } else if (status == AnimationStatus.dismissed) {
            // 反向執(zhí)行完畢左邊小球執(zhí)行
            _controller.forward();
          }
        });
  late var cure =
      CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic);
  late var cure2 =
      CurvedAnimation(parent: _controller2, curve: Curves.easeOutCubic);

  late Animation<double> animation = Tween(begin: 0.0, end: 1.0).animate(cure);

  late Animation<double> animation2 =
      Tween(begin: 0.0, end: 1.0).animate(cure2);

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsetsDirectional.only(top: 300, start: 150),
      child: CustomPaint(
        size: Size(100, 100),
        painter: _OvalLoadingPainter(
            animation, animation2, Listenable.merge([animation, animation2])),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    _controller2.dispose();
    super.dispose();
  }
}

class _OvalLoadingPainter extends CustomPainter {
  double radius = 6;
  final Animation<double> animation;
  final Animation<double> animation2;
  final Listenable listenable;

  late Offset offset; // 左邊小球圓心
  late Offset offset2; // 右邊小球圓心

  final double lineLength = 60; // 線長

  _OvalLoadingPainter(this.animation, this.animation2, this.listenable)
      : super(repaint: listenable) {
    offset = Offset(20, lineLength);
    offset2 = Offset(20 * radius * 8, lineLength);
  }

  // 擺動角度
  double angle = pi / 180 * 30; // 30°

  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()
      ..color = Colors.black87
      ..strokeWidth = 2;

    // 左邊小球 默認坐標 下方是90度 需要+pi/2
    var dx = 20 + 60 * cos(pi / 2 + angle * animation.value);
    var dy = 60 * sin(pi / 2 + angle * animation.value);
    // 右邊小球
    var dx2 = 20 + radius * 8 - 60 * cos(pi / 2 + angle * animation2.value);
    var dy2 = 60 * sin(pi / 2 + angle * animation2.value);

    offset = Offset(dx, dy);
    offset2 = Offset(dx2, dy2);

    /// 繪制線
      canvas.drawLine(Offset.zero, Offset(90, 0), paint);
    canvas.drawLine(Offset(20, 0), offset, paint);
    canvas.drawLine(
        Offset(20 + radius * 2, 0), Offset(20 + radius * 2, 60), paint);
    canvas.drawLine(
        Offset(20 + radius * 4, 0), Offset(20 + radius * 4, 60), paint);
    canvas.drawLine(
        Offset(20 + radius * 6, 0), Offset(20 + radius * 6, 60), paint);
    canvas.drawLine(Offset(20 + radius * 8, 0), offset2, paint);

    /// 繪制球
    canvas.drawCircle(offset, radius, paint);
    canvas.drawCircle(
        Offset(20 + radius * 2, 60),
        radius,
        paint);

    canvas.drawCircle(Offset(20 + radius * 4, 60), radius, paint);
    canvas.drawCircle(Offset(20 + radius * 6, 60), radius, paint);
    canvas.drawCircle(offset2, radius, paint);
  }
  @override
  bool shouldRepaint(covariant _OvalLoadingPainter oldDelegate) {
    return oldDelegate.listenable != listenable;
  }
}

去掉線的效果

總結

本文展示了實現(xiàn)牛頓擺的原理,其實并不復雜,關鍵點就是小球的運動軌跡和運動速度曲線,如果用到項目中當做Loading還有很多優(yōu)化的空間,比如加上小球影子、修改小球顏色或者把小球換成好玩的圖片等等操作會看起來更好看一點

到此這篇關于Flutter實現(xiàn)牛頓擺動畫效果的示例代碼的文章就介紹到這了,更多相關Flutter牛頓擺動畫內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論