【 Flutter 绘制 】点集的贝塞尔曲线拟合
主题列表:juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, greenwillow, v-green, vue-pro, healer-readable贡献主题:https://github.com/xitu/juejin-markdown-themes...
主题列表:juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, greenwillow, v-green, vue-pro, healer-readable
贡献主题:https://github.com/xitu/juejin-markdown-themes
theme: juejin
highlight:
本文作为对掘金小册 《Flutter 绘制指南 - 妙笔生花》 的一个知识补充点,后面会更新到小册中。在此也希望记录和分享一下 Flutter 中如何通过贝塞尔曲线使折线形成曲线。源码在这。
1. 问题描述
现在有一批如下的点,很容易通过
canvas.drawPoints
绘制出如下的折线。
dart ---->[ 点集 ]---- List<Offset> points1 = [ Offset(0, 20), Offset(40, 40) , Offset(80, -20), Offset(120, -40), Offset(160, -80), Offset(200, -20), Offset(240, -40), ];
但很多时候,我们希望用一个
曲线
来展示数据,而非生硬的折线。
所以本文就来探讨一下
如何使用贝塞尔曲线对点集进行拟合
。
2. 绘制点与折线
程序入口文件
main.dart
, 此处横屏全屏显示。
```dart ---->[p14bezier/s05bezier_line/main.dart]---- import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'paper.dart';
void main() { // 确定初始化 WidgetsFlutterBinding.ensureInitialized(); //横屏 SystemChrome.setPreferredOrientations( [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]); //全屏显示 SystemChrome.setEnabledSystemUIOverlays([]);
runApp(Paper()); } ```
显示组件
Paper
,使用PaperPainter
画板。
dart ---->[p14_bezier/s05_bezier_line/paper.dart]---- class Paper extends StatelessWidget { @override Widget build(BuildContext context) { return Container( color: Colors.white, child: CustomPaint( painter: PaperPainter() ), ); } }
通过简单的几步,如下的折线图便跃然纸上。其中
Coordinate
是我写的一个坐标系绘制辅助类,来方便查看点的位置,从而帮助理解。详见源码,不想用的话也不影响,删掉即可。
```dart ---->[p14bezier/s05bezier_line/paper.dart]---- class PaperPainter extends CustomPainter { final Coordinate coordinate = Coordinate(); List points1 = [ Offset(0, 20), Offset(40, 40) , Offset(80, -20), Offset(120, -40), Offset(160, -80), Offset(200, -20), Offset(240, -40), ];
Paint _helpPaint = Paint(); Paint _mainPaint = Paint(); Path _linePath = Path();
@override void paint(Canvas canvas, Size size) { coordinate.paint(canvas, size); // 画布原点 移到 屏幕中心 canvas.translate(size.width / 2, size.height / 2); // 绘制辅助点线 _drawHelp(canvas); }
void _drawHelp(Canvas canvas) { _helpPaint..style = PaintingStyle.stroke; // 绘制点 points1.forEach((element) { canvas.drawCircle(element, 2, _helpPaint..strokeWidth=1..color=Colors.orange); }); // 绘制折线 canvas.drawPoints(PointMode.polygon, points1, _helpPaint..strokeWidth=0.5..color=Colors.red); }
@override bool shouldRepaint(CustomPainter oldDelegate) => false; } ```
3. 贝塞尔曲线拟合
在下面方法中,传入一个
List<Offset>
类型的点集points
。其中首尾两段线使用二阶贝塞尔曲线,中间的使用三阶贝塞尔曲线。起止点和控制点通过current
当前点和next
下一点来控制。
dart void addBezierPathWithPoints(Path path, List<Offset> points) { for (int i = 0; i < points.length - 1; i++) { Offset current = points[i]; Offset next = points[i+1]; if (i == 0) { path.moveTo(current.dx, current.dy); // 控制点 double ctrlX = current.dx + (next.dx - current.dx) / 2; double ctrlY = next.dy; path.quadraticBezierTo(ctrlX, ctrlY, next.dx, next.dy); } else if (i < points.length - 2) { // 控制点 1 double ctrl1X = current.dx + (next.dx - current.dx) / 2; double ctrl1Y = current.dy; // 控制点 2 double ctrl2X = ctrl1X; double ctrl2Y = next.dy; path.cubicTo(ctrl1X,ctrl1Y,ctrl2X,ctrl2Y,next.dx,next.dy); }else{ path.moveTo(current.dx, current.dy); // 控制点 double ctrlX = current.dx + (next.dx - current.dx) / 2; double ctrlY = current.dy; path.quadraticBezierTo(ctrlX, ctrlY, next.dx, next.dy); } } }
首先来看第一段曲线
(0, 20) 是起点 current
,(40, 40) 是下一点 next
,对于二阶贝塞尔曲线来说,只要确定控制点就完事了。这里控制点 x 取两点的中点横坐标,y 取 next 的纵坐标
,即下面的(10,40)
点。
dart if (i == 0) { path.moveTo(current.dx, current.dy); // 控制点 double ctrlX = current.dx + (next.dx - current.dx) / 2; double ctrlY = next.dy; path.quadraticBezierTo(ctrlX, ctrlY, next.dx, next.dy); }
再看最后一段曲线 ,和第一段类似,三点的位置如下,
注意这里使用的是相对于倒数第二个点的添加 relativeQuadraticBezierTo,来保证曲线的连贯性
。
dart // 控制点 double ctrlX = (next.dx - current.dx) / 2; double ctrlY = 0; path.relativeQuadraticBezierTo(ctrlX, ctrlY, next.dx-current.dx, next.dy-current.dy);
第二段曲线使用
三阶贝塞尔
,控制点如下所示。
dart // 控制点 1 double ctrl1X = current.dx + (next.dx - current.dx) / 2; double ctrl1Y = current.dy; // 控制点 2 double ctrl2X = ctrl1X; double ctrl2Y = next.dy; path.cubicTo(ctrl1X,ctrl1Y,ctrl2X,ctrl2Y,next.dx,next.dy);
同样后面的几条线段都是类似,控制点如下,这样就生成了连续的曲线。这里通过
addBezierPathWithPoints
方法就可以实现将一个点集编程一个曲线路径添加到指定Path
中。
这样使用多个点集也就会形成多个曲线。
4. 在统计图中使用
这样在后面 16 章实现的折线统计图就可以使用曲线来替换折线,代码见
p16_chart.s03_line_plus
本篇到此结束,不止是 Flutter 中的贝塞尔曲线,其他平台、框架中的贝塞尔曲线也是类似的,所以这个知识点虽然比较很小,但很重要。很好地理解它,能提升你对贝塞尔曲线的认识,一把利器握在手里,你是要驾驭它,而不是畏惧它。
更多推荐
所有评论(0)