小常识,大应战!本文正在参与“程序员必备小常识”创造活动。
- 看完本文后,你将知道什么是贝塞尔曲线、Flutter中怎么完结贝塞尔曲线以及怎么结合动画运用~
概念公式
Bézier curve(贝塞尔曲线)是应用于二维图形应用程序的数学曲线。 曲线界说:起始点、停止点(也称锚点)、控制点。通过调整控制点,贝塞尔曲线的形状会发生改变。 1962年,法国数学家Pierre Bézier第一个研讨了这种矢量制作曲线的办法,并给出了具体的核算公式,因此依照这样的公式制作出来的曲线就用他的姓氏来命名,称为贝塞尔曲线。
N个点,即能确认一个N-1阶贝塞尔曲线
二阶贝塞尔
始终遵循 AD:DB = BE:EC = DF:FE = t,t为0-1
(P0起点,P1控制点,P2结尾)
三阶贝塞尔
n阶贝塞尔
flutter中的贝塞尔示例(含注释)
class BezierCurvePage extends StatefulWidget {
const BezierCurvePage({Key? key}) : super(key: key);
@override
_BezierCurvePageState createState() => _BezierCurvePageState();
}
class _BezierCurvePageState extends State<BezierCurvePage>
with TickerProviderStateMixin {
List<Offset> _points = <Offset>[];
AnimationController? _animationController;
Animation<double>? _animation;
int n = 3;//n-1 = 2或3阶
@override
void initState() {
_animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 3),
);
_animation = Tween(begin: 0.0, end: 1.0).animate(_animationController!)
..addListener(() {
setState(() {});
});
super.initState();
}
@override
void dispose() {
_animationController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("贝塞尔曲线"),
),
body: Stack(
children: [
Listener(
child: Container(
alignment: Alignment.center,
color: Colors.white,
width: ScreenUtil().screenWidth,
height: ScreenUtil().screenHeight,
),
onPointerDown: (PointerDownEvent event) {
setState(() {
if (_points.length < n) {
_points.add(event.localPosition);
if(_points.length == n){
startAnim();
}
}else{
_animationController?.reset();
_points.clear();
_points.add(event.localPosition);
}
});
},
),
CustomPaint(
painter: DrawBezierPainter(_points, _animation?.value ?? 0.0,n),
),
],
),
);
}
startAnim() {
_animationController?.forward();
}
}
class DrawBezierPainter extends CustomPainter {
Paint dotPaint = new Paint();
Paint linePaint = new Paint();
Paint subLinePaint = new Paint()..color = Colors.grey;
Paint bezierPaint = new Paint();
double progress = 0.0;
int n;
List<Offset> points = [];
DrawBezierPainter(this.points, this.progress,this.n);
@override
void paint(Canvas canvas, Size size) {
dotPaint.color = Colors.blue;
dotPaint.strokeCap = StrokeCap.square;
dotPaint.strokeWidth = 5.0;
for (int i = 0; i < (points.length); i++) {
canvas.drawCircle(points[i], 5, dotPaint);
if (i + 1 < points.length) {
canvas.drawLine(points[i], points[i + 1], linePaint);
}
}
if (progress > 0) {
List<Offset> subLevelPoints =
getAllPointsWithOriginPoints(points, progress);
if (subLevelPoints.length == n) {//3 2阶
canvas.drawLine(subLevelPoints[0], subLevelPoints[1], subLinePaint);
canvas.drawCircle(subLevelPoints[2], 5, bezierPaint);
}else if (subLevelPoints.length == 6){//6 3阶
canvas.drawLine(subLevelPoints[0], subLevelPoints[1], subLinePaint);
canvas.drawLine(subLevelPoints[1], subLevelPoints[2], subLinePaint);
canvas.drawLine(subLevelPoints[3], subLevelPoints[4], subLinePaint
..color = Colors.blue
..strokeWidth = 1);
canvas.drawCircle(subLevelPoints[5], 5, bezierPaint);
}
Path bezierPath = new Path();
bezierPath.moveTo(points[0].dx, points[0].dy); //起点
if(n == 3){
bezierPath.quadraticBezierTo(
points[1].dx,
points[1].dy,
points[2].dx,
points[2].dy); //控制点,结尾
}else if (n == 4){
bezierPath.cubicTo(points[1].dx, points[1].dy, points[2].dx, points[2].dy, points[3].dx, points[3].dy);
}
canvas.drawPath(
bezierPath,
bezierPaint
..strokeWidth = 1
..color = Colors.red
..style = PaintingStyle.stroke);
// isAntiAlias(抗锯齿) color(色彩) blendMode(混合模式) style(画笔样式)
// strokeWidth(线宽) strokeCap(线帽类型) strokeJoin(线接类型) strokeMiterLimit(斜接约束)
// maskFilter(遮罩滤镜) shader(着色器) colorFilter(色彩滤镜) imageFilter(图片滤镜)
// invertColors(是否反色) filterQuality(滤镜质量)
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
/**
获取根据touch点衍生出来所有的点
@param points 手动点击的点
@param progress 贝塞尔曲线当前进度
@return pointArray
*/
List<Offset> getAllPointsWithOriginPoints(
List<Offset> points, double progress) {
List<Offset> resultArray = [];
int level = points.length;
List<Offset> tempArray = points;
for (int i = 0; i < level; i++) {
tempArray = getsubLevelPointsWithSuperPoints(tempArray, progress);
// 中心阶层的点
resultArray.addAll(tempArray);
if (tempArray.length == 1) {
// 终究贝塞尔曲线的点
// [_bezierPathPoints addObjectsFromArray: tempArray];
break;
}
}
return resultArray;
}
/**
根据上一级的点获取下一级的点
*/
List<Offset> getsubLevelPointsWithSuperPoints(
List<Offset> points, double progress) {
List<Offset> tempArr = [];
for (int i = 0; i < points.length - 1; i++) {
Offset prePoint = points[i];
Offset lastPoint = points[i + 1];
double diffX = lastPoint.dx - prePoint.dx;
double diffY = lastPoint.dy - prePoint.dy;
Offset currentPoint = Offset(
prePoint.dx + diffX * progress, prePoint.dy + diffY * progress);
tempArr.add(currentPoint);
}
return tempArr;
}
}
Flutter中运用贝塞尔+动画(含注释)
在日常开发中,咱们能够运用贝塞尔曲线结合动画完结一些需求.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class RotatePage extends StatefulWidget {
RotatePage({Key? key}) : super(key: key);
@override
_RotatePageState createState() => _RotatePageState();
}
class _RotatePageState extends State<RotatePage> with TickerProviderStateMixin {
AnimationController? _leftController,
_rightController,
_propellerController; // 动画 controller
Animation<double>? _leftAnimation, _rightAnimation, _propellerAnimation; // 动画
double? leftViewToLeft; // 小圆点的left(动态核算)
double? leftViewToTop; // 小远点的right(动态核算)
double? rightViewToLeft; // 小圆点的left(动态核算)
double? rightViewToTop; // 小远点的right(动态核算)
double angle = 0;
double shipSize = 50;
double heartWidth = ScreenUtil().screenWidth;
double heartHeight = ScreenUtil().screenWidth + 200;
@override
void initState() {
super.initState();
addLeftAnimation();
addRightAnimation();
addPropellerAnimation();
}
addLeftAnimation() {
_leftController =
AnimationController(duration: Duration(seconds: 5), vsync: this);
_leftAnimation = Tween(begin: 1.0, end: 0.0).animate(_leftController!);
var x0Left = heartWidth / 2;
var y0Left = heartHeight / 4;
var x1Left = heartWidth / 7;
var y1Left = heartHeight / 9;
var x2Left = heartWidth / 21;
var y2Left = (heartHeight * 2) / 5;
var x3Left = heartWidth / 2;
var y3Left = heartHeight * 7 / 12;
_leftAnimation!.addListener(() {
if (_leftAnimation!.isCompleted) {
_leftController!.reverse();
}
// t 动态改变的值
var t = _leftAnimation!.value;
if (mounted)
setState(() {
// B3(t) = (1-t)^3*P0+3t(1-t)^2*P1+3t^2(1-t)*P2+t^3*P3, t归于 [0,1]
rightViewToLeft = pow(1 - t, 3) * x0Left +
3 * t * pow(1 - t, 2) * x1Left +
3 * pow(t, 2) * (1 - t) * x2Left +
pow(t, 3) * x3Left;
rightViewToTop = pow(1 - t, 3) * y0Left +
3 * t * pow(1 - t, 2) * y1Left +
3 * pow(t, 2) * (1 - t) * y2Left +
pow(t, 3) * y3Left;
});
});
// 初始化小圆点的方位
rightViewToLeft = x0Left;
rightViewToTop = y0Left;
//显现小圆点的时分动画就开端
_leftController!.forward();
}
addRightAnimation() {
_rightController =
AnimationController(duration: Duration(seconds: 5), vsync: this);
_rightAnimation = Tween(begin: 1.0, end: 0.0).animate(_rightController!);
var x0Right = heartWidth / 2;
var y0Right = heartHeight / 4;
var x1Right = (heartWidth * 6) / 7;
var y1Right = heartHeight / 9;
var x2Right = (heartWidth * 13) / 13;
var y2Right = (heartHeight * 2) / 5;
var x3Right = heartWidth / 2;
var y3Right = heartHeight * 7 / 12;
_rightAnimation!.addListener(() {
if (_rightAnimation!.isCompleted) {
_rightController!.reverse();
}
// t 动态改变的值
var t = _rightAnimation!.value;
if (mounted)
setState(() {
// B3(t) = (1-t)^3*P0+3t(1-t)^2*P1+3t^2(1-t)*P2+t^3*P3, t归于 [0,1]
leftViewToLeft = pow(1 - t, 3) * x0Right +
3 * t * pow(1 - t, 2) * x1Right +
3 * pow(t, 2) * (1 - t) * x2Right +
pow(t, 3) * x3Right;
leftViewToTop = pow(1 - t, 3) * y0Right +
3 * t * pow(1 - t, 2) * y1Right +
3 * pow(t, 2) * (1 - t) * y2Right +
pow(t, 3) * y3Right;
});
});
// 初始化小圆点的方位
leftViewToLeft = x0Right;
leftViewToTop = y0Right;
//显现小圆点的时分动画就开端
_rightController!.forward();
}
addPropellerAnimation() {
_propellerController =
AnimationController(vsync: this, duration: Duration(milliseconds: 800));
_propellerAnimation =
Tween(begin: 0.0, end: pi).animate(_propellerController!);
_propellerController?.repeat(); // 循环播映动画
}
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
_childWidget(leftViewToLeft!, leftViewToTop!),
_childWidget(rightViewToLeft!, rightViewToTop!),
CustomPaint(
painter: HeartShapedPainter(heartWidth, heartHeight),
size: Size(heartWidth, heartHeight),
)
],
);
}
Widget _childWidget(double toLeft, double toTop) {
return Positioned(
left: toLeft - shipSize / 2,
top: toTop - shipSize / 2,
child: Stack(children: [
Image.network(
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic38.nipic.com%2F20140226%2F18047467_102622574149_2.jpg&refer=http%3A%2F%2Fpic38.nipic.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1634376105&t=277f3070e1c9b37b0971c8ac79086c81",
width: shipSize,
height: shipSize,
),
Positioned(
left: shipSize / 2,
top: shipSize / 2,
child: AnimatedBuilder(
animation: _leftAnimation!,
builder: (context, _) => Transform.rotate(
alignment: Alignment.topLeft,
angle: _propellerAnimation!.value,
child: Container(
color: Colors.yellow,
width: 200,
height: 10,
),
)),
),
]),
);
}
@override
void dispose() {
_leftController?.dispose();
_rightController?.dispose();
_propellerController?.dispose();
super.dispose();
}
}
class HeartShapedPainter extends CustomPainter {
double width;
double height;
HeartShapedPainter(this.width, this.height);
Paint _paint = Paint()..strokeWidth = 2.0;
@override
void paint(Canvas canvas, Size size) {
/// 三阶贝塞尔曲线
///p0 = width / 2, height / 4
///p1 = (width * 6) / 7, height / 9)
///p2 = (width * 13) / 13, (height * 2) / 5)
///p3 = width / 2, (height * 7) / 12
/// 右边
Path path1 = new Path();
path1.moveTo(width / 2, height / 4);
path1.cubicTo((width * 6) / 7, height / 9, (width * 13) / 13,
(height * 2) / 5, width / 2, (height * 7) / 12);
canvas.drawPath(
path1,
_paint
..color = Colors.redAccent
..style = PaintingStyle.stroke);
///左边
Path path2 = new Path();
path2.moveTo(width / 2, height / 4);
path2.cubicTo(width / 7, height / 9, width / 21, (height * 2) / 5,
width / 2, (height * 7) / 12);
canvas.drawPath(
path2,
_paint
..color = Colors.redAccent
..style = PaintingStyle.stroke);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}