Flutter实现贝塞尔曲线动画

先上个效果图

001.gif

贝塞尔曲线(贝塞尔可不发音)
绘制二阶曲线通常需要三个点:起点、终点、控制点
控制点指的是起点和终点切线的交点(见下图)

002.png

主要代码

002.png

完整代码

import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

class HomeWidget extends StatefulWidget {
  const HomeWidget({super.key});
  @override
  HomeWidgetState createState() => HomeWidgetState();
}

class HomeWidgetState extends State<HomeWidget> with TickerProviderStateMixin {
  List<double> lefts = [20, 150, 280]; //距离左边的距离
  List<double> tops = [225, 350, 225]; //距离顶部的距离
  Offset pointEnd = const Offset(175, 70); //终点坐标
  List<double> _sizes = [50, 50, 50]; //尺寸

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: Column(
        children: [
          const Padding(padding: EdgeInsets.only(top: 100)),
          Center(
            child: SizedBox(
              width: 350,
              height: 500,
              child: Stack(
                children: [
                  Positioned(
                    left: 105,
                    top: 0,
                    child: _ballView(size: 140, color: Colors.red),
                  ),
                  Positioned(
                    left: lefts[0],
                    top: tops[0],
                    child: GestureDetector(
                      onTap: () => _updatePositon(0),
                      child: _ballView(size: _sizes[0], color: Colors.purple),
                    ),
                  ),
                  Positioned(
                    left: lefts[1],
                    top: tops[1],
                    child: GestureDetector(
                      onTap: () => _updatePositon(1),
                      child:
                          _ballView(size: _sizes[1], color: Colors.lightBlue),
                    ),
                  ),
                  Positioned(
                    left: lefts[2],
                    top: tops[2],
                    child: GestureDetector(
                      onTap: () => _updatePositon(2),
                      child: _ballView(size: _sizes[2], color: Colors.indigo),
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _ballView({required double size, required Color color}) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(size * 0.5),
      child: Container(width: size, height: size, color: color),
    );
  }

  void _updatePositon(int index) {
    AnimationController controller = AnimationController(
        vsync: this, duration: const Duration(milliseconds: 500));
    Animation animation = Tween(begin: 0.0, end: 1.0).animate(controller);
    // 二阶贝塞尔曲线用值
    var x0 = lefts[index];
    var y0 = tops[index];

    var x2 = pointEnd.dx;
    var y2 = pointEnd.dy;

    // x1和y1可根据实际效果自行调整
    var x1 = x2 - (x2 - x0) * 0.5;
    var y1 = 0;
    animation.addListener(() {
      var t = animation.value; // t--动态变化的值
      if (mounted) {
        setState(() {
          // 二阶贝塞尔曲线
          // pow(x,2):表示x的2次方、x0:起点坐标、x1:控制点坐标、x2:终点坐标
          lefts[index] =
              pow(1 - t, 2) * x0 + 2 * t * (1 - t) * x1 + pow(t, 2) * x2;
          tops[index] =
              pow(1 - t, 2) * y0 + 2 * t * (1 - t) * y1 + pow(t, 2) * y2;
          _sizes[index] = 50.0 * (1 - t);
        });
      }
    });
    // 开始动画
    controller.forward().whenComplete(() {
      if (listEquals(_sizes, [0, 0, 0])) {
        setState(() {
          lefts = [20, 150, 280];
          tops = [225, 350, 225];
          pointEnd = const Offset(175, 70);
          _sizes = [50, 50, 50];
        });
      }
    });
  }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容