先上个效果图
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];
});
}
});
}
}