Flutter 绘制探索 | 操作坐标系范围


theme: cyanosis

highlight: mono-blue

前言

在视频 【Flutter 绘制指南 | 第二集 · 坐标系】 中,实现了画板区域内的单位坐标系。今天来拓展一下,让坐标系支持变换,比如坐标系的平移和缩放,从而让坐标系的功能更加完备。

image.png

本文要实现的效果如下,可以通过下方的七个按钮操作坐标系的范围,这样可以查看在当前定义域内的函数曲线,也就是移动坐标轴的功能:本文详细源码见 toly1994328/skeleton/lib/paint/coo

63.gif


1. 坐标范围的定义

目前坐标的范围是 x:[0~1] , y:[0~1],而且是写死的数据。为了可以让坐标轴的范围值可调整,首先需要对刻度的表现进行优化。比如,使用者可以通过一个配置项指定横纵坐标的最大最小值,我们将这个配置项数据赋值为 AxisRange 类型,定义如下:

image.png

其中给出 xSpanySpanget 方法,用于获取横纵坐标轴的跨度。然后让 Coordinate 类持有坐标轴范围对象:

image.png

``` class AxisRange { final double maxX; final double minX; final double maxY; final double minY;

const AxisRange({ this.maxX = 1.0, this.minX = -1.0, this.maxY = 1.0, this.minY = -1.0, });

double get xSpan => maxX - minX; double get ySpan => maxY - minY; } ```


2. 刻度值的绘制优化

接下来,就要根据坐标轴的范围来绘制刻度和网格。拿坐标系横轴来说,刻度个数 xScaleCount 由用户指定,这样很容易计算出每个刻度间的步长 step。横轴最左侧是坐标轴横轴范围的最小值,最右侧是范围最大值。然后遍历 xScaleCount 次即可:

image.png

如下是 x 轴刻度遍历绘制的逻辑刻度和竖向网格线的逻辑:其中刻度的偏移量,由当前刻度值处占总长的分率乘以区域宽度计算得出。

大人头 double stepX = (range.xSpan / xScaleCount); for(int i =0;i<=xScaleCount;i++){ double value = range.minX+stepX*i; double offsetX = (value - range.minX) / range.xSpan * size.width; textPainter.text = TextSpan( text: value.toStringAsFixed(2), style: const TextStyle(fontSize: 12, color: Colors.black), ); textPainter.layout(); // 绘制文字 textPainter.paint( canvas, Offset(offsetX - textPainter.size.width / 2, 5), ); // 绘制网格 canvas.drawLine( Offset(offsetX, 0), Offset(offsetX, -size.height), gridAxisPaint, ); }


纵轴的刻度的绘制同理,这样只要指定坐标轴的范围,然后更新画板,坐标轴的对应刻度就会发生改变:

image.png


3. 坐标轴上的点

接下来就是最关键的一步,如何在坐标轴上描点。由于展示的坐标系上的点和实际的画板中绘制的逻辑像素并不相同,所以需要对坐标系上的点进行一下转换,使其称为画板中的绝对坐标。转换起来也并不是很困难,根据当前坐标占横纵坐标的分率,再结合画板尺寸就很容易算出当前点在画板中的绝对坐标:

image.png

void _drawPoint(Canvas canvas, Size size){ Offset p = const Offset(0.6, 0.6); double x = (p.dx - coordinate.range.minX)/coordinate.range.xSpan*size.width; double y = (p.dy - coordinate.range.minY)/coordinate.range.ySpan*size.height; canvas.drawCircle(Offset(x, y), 10, _mainPainter); }


这样无论坐标轴的范围如何变化,都不会影响坐标点在坐标系中的位置,如下横坐标范围在 0.10~2.10 之间:

image.png


4. 绘制函数

有了坐标轴,其实函数图像的绘制还是比较简单的,无非就是在当前坐标系下收集点,然后根据点画线罢了。比如在坐标系中绘制一个 sin 函数曲线,定义域在 [-1~1] 之间:

image.png

绘制逻辑如下,给定 pointCount 表示曲线中点的个数,个数越多曲线越精细,相对来说绘制也就越耗时。思路是遍历 pointCount 次,每次遍历时使用函数关系取点,将点连接就可以得到折线,只不过取的点很多就呈现了曲线效果。后面也可以少取一下点,通过贝塞尔曲线拟合点。

dart void _drawFn(Canvas canvas, Size size,){ List<Offset> points = []; int pointCount = 500; double step = coordinate.range.xSpan/pointCount; for(int i =0;i<pointCount;i++){ double dx = coordinate.range.minX+step*i; double dy = 0.6*sin(dx*5); double x = (dx - coordinate.range.minX)/coordinate.range.xSpan*size.width; double y = (dy - coordinate.range.minY)/coordinate.range.ySpan*size.height; points.add(Offset(x, y)); } canvas.drawPoints(PointMode.polygon, points, _mainPainter..color=Colors.blue); }


这样多收集几条点集,就可以绘制多条曲线,比如下面红色是 y=x 函数,紫色是 y = x*x*x/20 函数:

image.png


5. 操作坐标系范围

现在一切准备就绪,接下来只要通过点击按钮,操作坐标系范围,即可实现如下效果:

63.gif

Coordinate 类在提供 movescale 方法,移动就是将坐标轴的范围根据偏移量进行平移,缩放是将坐标轴范围乘以 rate 倍率:

``` ---->[coordinate.dart]---- void move(Offset offset) { range = AxisRange( minY: range.minY + offset.dy, maxY: range.maxY + offset.dy, minX: range.minX + offset.dx, maxX: range.maxX + offset.dx, ); }

void scale(double rate) { range = AxisRange( minY: range.minY * rate, maxY: range.maxY * rate, minX: range.minX * rate, maxX: range.maxX * rate, ); } ```


在点击按钮时触发 _onTapCtrl 方法,其中根据 CtrlType 执行对应方法即可,比如点击左上右下时让坐标轴进行移动:

``` void _onTapCtrl(CtrlType type) { if(type == CtrlType.right){ coordinate.move(const Offset(0.1, 0)); } if(type == CtrlType.left){ coordinate.move(const Offset(-0.1, 0)); } if(type == CtrlType.up){ coordinate.move(const Offset(0, 0.1)); } if(type == CtrlType.down){ coordinate.move(const Offset(0, -0.1)); }

pointValues.repaint(); } ```


到这里,一个简单的坐标轴范围操作的案例就完成了,还有一些值得优化的部分:比如现在刻度是严格按照份数进行分割的,刻度数值比较乱;另外还可以让用户通过输入框确定坐标范围;对函数图像的绘制方式有待优化;这些会在后续逐步完善。那本文就到这里,谢谢观看 ~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值