Flutter贝塞尔曲线绘制插件bezier_kit的使用
Flutter贝塞尔曲线绘制插件bezier_kit的使用
bezier_kit
是一个用Dart编写的全面的贝塞尔路径库。
关于
bezier_kit
是从原始的Swift版本 BezierKit
手动转换而来的。它基于2022年6月28日(版本0.15.0)的 BezierKit
分支。
特性
- ✅ 构造线段、二次和三次贝塞尔曲线
- ✅ 确定曲线上任意位置、导数和法线
- ✅ 使用Legendre-Gauss求积计算曲线长度
- ✅ 计算曲线交点,并精确到任意精度
- ✅ 确定边界框和极值
- ✅ 定位到给定点最近的曲线位置
- ✅ 拆分曲线为子曲线
- ✅ 偏移和轮廓曲线
- ❌ 全面的单元和集成测试覆盖
- ❌ 完整的文档
安装
Dart
dart pub add bezier_kit
Flutter
flutter pub add bezier_kit
使用
构造与绘制曲线
bezier_kit
支持三次贝塞尔曲线 (CubicCurve
) 和二次贝塞尔曲线 (QuadraticCurve
),以及线段 (LineSegment
),每种类型都实现了 BezierCurve
协议,该协议包含大多数API功能。
import 'package:bezier_kit/bezier_kit.dart';
final curve = CubicCurve(
p0: Point(x: 100, y: 25),
p1: Point(x: 10, y: 90),
p2: Point(x: 110, y: 100),
p3: Point(x: 150, y: 195),
);
final canvas: Canvas = ... // 你的绘图上下文
final draw = Draw(canvas);
draw.drawSkeleton(curve: curve); // 绘制控制点
draw.drawCurve(curve: curve); // 绘制曲线本身
曲线相交
intersectionsWithCurve(curve)
方法可以确定 self
和 curve
之间的每个交点,返回一个 Intersection
对象数组。每个交点有两个字段:t1
表示 self
在交点处的 t 值,t2
表示 curve
在交点处的 t 值。你可以通过传递对应的 t 值来计算交点的坐标。
三次曲线可能会自交,可以通过调用 selfIntersections()
方法来确定。
final intersections = curve1.intersectionsWithCurve(curve2);
final points = intersections.map((i) => curve1.point(at: i.t1));
draw.drawCurve(curve: curve1);
draw.drawCurve(curve: curve2);
for (final p in points) {
draw.drawPoint(origin: p);
}
曲线分割
split(from:, to:)
方法可以在给定的 t 值范围内生成子曲线。split(at:)
方法可以用于生成由单个 t 值分割出的左子曲线和右子曲线。
draw.setColor(color: Draw.lightGrey);
draw.drawSkeleton(curve: curve);
draw.drawCurve(curve: curve);
final subcurve = curve.split(from: 0.25, to: 0.75); // 或者尝试 (leftCurve, rightCurve) = curve.split(at:)
draw.setColor(color: Draw.red);
draw.drawCurve(curve: subcurve);
draw.drawCircle(center: curve.point(at: 0.25), radius: 3);
draw.drawCircle(center: curve.point(at: 0.75), radius: 3);
确定边界框
final boundingBox = curve.boundingBox;
draw.drawSkeleton(context, curve: curve);
draw.drawCurve(context, curve: curve);
draw.setColor(context, color: Draw.pinkish);
draw.drawBoundingBox(context, boundingBox: curve.boundingBox);
更多
bezier_kit
是一个功能强大的库,拥有大量功能。目前最好的方式是构建示例 Flutter 应用并检查提供的每个演示。
测试
bezier_kit
包含从原始源代码转换过来的整个测试套件,从Swift转换为Dart。
要运行测试套件,请执行:
dart test
许可证
bezier_kit
是在MIT许可证下发布的。详情请参阅 LICENSE文件。
示例代码
以下是完整的示例代码,展示了如何使用 bezier_kit
插件。
// Copyright (c) 2023-2024 Guilherme Lepsch. All rights reserved. Use of
// this source code is governed by MIT license that can be found in the
// [LICENSE file](https://github.com/lepsch/bezier_kit/blob/main/LICENSE).
import 'package:bezier_kit/bezier_kit.dart';
import 'package:example/demos.dart';
import 'package:example/draw.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'bezier_kit Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
[@override](/user/override)
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _demoIndex = 0;
final _draggables = all[0].cubicControlPoints.toList();
var _useQuadratic = false;
var _isPanning = false;
Point? _lastInputLocation;
void resetDraggables() {
_draggables.clear();
final demo = all[_demoIndex];
(_useQuadratic ? demo.quadraticControlPoints : demo.cubicControlPoints)
.forEach(_draggables.add);
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text("bezier_kit Flutter Demo"),
),
body: ConstrainedBox(
constraints: BoxConstraints.expand(),
child: LayoutBuilder(builder: (context, constraints) {
final dx = (constraints.maxWidth - intrinsicContentSize.width) / 2;
final dy = (constraints.maxHeight - intrinsicContentSize.height) / 2;
return MouseRegion(
onExit: (event) => setState(() => _lastInputLocation = null),
onHover: (event) => setState(() => _lastInputLocation = event.localPosition.toPoint()),
child: Stack(
children: [
Container(
color: Colors.white,
),
CustomPaint(
size: Size(constraints.maxWidth, constraints.maxHeight),
painter: Painter(
_demoIndex,
_draggables,
_useQuadratic,
_lastInputLocation,
),
),
for (final (i, draggable) in _draggables.indexed)
Positioned(
left: draggable.x + dx - draggableWidth / 2,
top: draggable.y + dy - draggableWidth / 2,
child: MouseRegion(
cursor: _isPanning
? SystemMouseCursors.grabbing
: SystemMouseCursors.grab,
child: GestureDetector(
onPanStart: (_) => setState(() => _isPanning = true),
onPanEnd: (_) => setState(() => _isPanning = false),
onPanUpdate: (details) => setState(
() => _draggables[i] += details.delta.toPoint()),
child: Container(
width: 20,
height: 20,
color: Colors.transparent,
),
),
))
],
),
);
}),
),
bottomNavigationBar: IntrinsicHeight(
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IntrinsicWidth(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ListTile(
title: const Text('Quadratic'),
leading: Radio<bool>(
value: true,
groupValue: _useQuadratic,
onChanged: (value) => setState(() {
_useQuadratic = value!;
resetDraggables();
}),
),
),
ListTile(
title: const Text('Cubic'),
leading: Radio<bool>(
value: false,
groupValue: _useQuadratic,
onChanged: (value) => setState(() {
_useQuadratic = value!;
resetDraggables();
}),
),
),
],
),
),
DropdownButton<int>(
value: _demoIndex,
icon: const Icon(Icons.arrow_downward),
onChanged: (value) {
setState(() {
_demoIndex = value!;
resetDraggables();
});
},
items: all.indexed
.map<DropdownMenuItem<int>>((demo) => DropdownMenuItem<int>(
value: demo.$1, child: Text(demo.$2.title)))
.toList(),
)
],
),
),
);
}
}
class Painter extends CustomPainter {
final int demoIndex;
final bool useQuadratic;
final List<Point> draggables;
final Point? lastInputLocation;
const Painter(this.demoIndex, this.draggables, this.useQuadratic, this.lastInputLocation);
[@override](/user/override)
void paint(Canvas canvas, Size size) {
final demo = all[demoIndex];
// 将曲线居中
final dx = (size.width - intrinsicContentSize.width) / 2;
final dy = (size.height - intrinsicContentSize.height) / 2;
canvas.translate(dx, dy);
final curve = draggables.isNotEmpty
? useQuadratic
? QuadraticCurve.fromList(points: draggables)
: CubicCurve.fromList(draggables)
: null;
demo.drawFunction(
canvas,
DemoState(
lastInputLocation: lastInputLocation != null
? Point(x: lastInputLocation!.x - dx, y: lastInputLocation!.y - dy)
: null,
quadratic: useQuadratic,
curve: curve,
),
);
}
[@override](/user/override)
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
const intrinsicContentSize = Size(200, 210);
const draggableWidth = 20;
更多关于Flutter贝塞尔曲线绘制插件bezier_kit的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter贝塞尔曲线绘制插件bezier_kit的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
bezier_kit
是一个用于在 Flutter 中绘制贝塞尔曲线的插件。它提供了灵活的方式来绘制二次贝塞尔曲线、三次贝塞尔曲线等,并且可以轻松地自定义曲线的样式和动画效果。以下是如何使用 bezier_kit
插件来绘制贝塞尔曲线的基本步骤。
1. 添加依赖
首先,你需要在 pubspec.yaml
文件中添加 bezier_kit
的依赖:
dependencies:
flutter:
sdk: flutter
bezier_kit: ^0.1.0 # 请检查最新版本
然后运行 flutter pub get
来获取依赖。
2. 导入包
在你的 Dart 文件中导入 bezier_kit
包:
import 'package:bezier_kit/bezier_kit.dart';
3. 绘制贝塞尔曲线
你可以使用 BezierPainter
来绘制贝塞尔曲线。以下是一个简单的例子:
import 'package:flutter/material.dart';
import 'package:bezier_kit/bezier_kit.dart';
class BezierCurveExample extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Bezier Curve Example'),
),
body: Center(
child: CustomPaint(
size: Size(300, 300),
painter: BezierCurvePainter(),
),
),
);
}
}
class BezierCurvePainter extends CustomPainter {
[@override](/user/override)
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..strokeWidth = 4.0
..style = PaintingStyle.stroke;
// 定义控制点
final startPoint = Offset(50, 150);
final controlPoint1 = Offset(100, 50);
final controlPoint2 = Offset(200, 250);
final endPoint = Offset(250, 150);
// 创建三次贝塞尔曲线
final cubicBezier = CubicBezier(
startPoint: startPoint,
controlPoint1: controlPoint1,
controlPoint2: controlPoint2,
endPoint: endPoint,
);
// 绘制贝塞尔曲线
cubicBezier.paint(canvas, paint);
}
[@override](/user/override)
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
void main() {
runApp(MaterialApp(
home: BezierCurveExample(),
));
}
4. 运行应用
运行你的 Flutter 应用程序,你应该会看到一个绘制了三次贝塞尔曲线的界面。
5. 自定义曲线
你可以通过调整 startPoint
、controlPoint1
、controlPoint2
和 endPoint
的位置来改变曲线的形状。你也可以使用 QuadraticBezier
来绘制二次贝塞尔曲线。
// 创建二次贝塞尔曲线
final quadraticBezier = QuadraticBezier(
startPoint: startPoint,
controlPoint: controlPoint1,
endPoint: endPoint,
);
// 绘制二次贝塞尔曲线
quadraticBezier.paint(canvas, paint);
6. 添加动画
你可以结合 Flutter 的动画系统来创建动态的贝塞尔曲线。例如,可以使用 AnimationController
和 Tween
来动态改变控制点的位置,从而让曲线动起来。
class AnimatedBezierCurve extends StatefulWidget {
[@override](/user/override)
_AnimatedBezierCurveState createState() => _AnimatedBezierCurveState();
}
class _AnimatedBezierCurveState extends State<AnimatedBezierCurve>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<Offset> _animation;
[@override](/user/override)
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true);
_animation = Tween<Offset>(
begin: Offset(100, 50),
end: Offset(200, 250),
).animate(_controller);
}
[@override](/user/override)
void dispose() {
_controller.dispose();
super.dispose();
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Animated Bezier Curve'),
),
body: Center(
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return CustomPaint(
size: Size(300, 300),
painter: AnimatedBezierCurvePainter(_animation.value),
);
},
),
),
);
}
}
class AnimatedBezierCurvePainter extends CustomPainter {
final Offset controlPoint;
AnimatedBezierCurvePainter(this.controlPoint);
[@override](/user/override)
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..strokeWidth = 4.0
..style = PaintingStyle.stroke;
final startPoint = Offset(50, 150);
final endPoint = Offset(250, 150);
final cubicBezier = CubicBezier(
startPoint: startPoint,
controlPoint1: controlPoint,
controlPoint2: Offset(200, 250),
endPoint: endPoint,
);
cubicBezier.paint(canvas, paint);
}
[@override](/user/override)
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
void main() {
runApp(MaterialApp(
home: AnimatedBezierCurve(),
));
}