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

前言

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

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


1. 坐标范围的定义

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

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

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 次即可:

如下是 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,
  );
}

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5hBBYcsl-1672920319553)(null)]


3. 坐标轴上的点

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

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 之间:


4. 绘制函数

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

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

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 函数:


5. 操作坐标系范围

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

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();
}

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

作者:张风捷特烈
链接:https://juejin.cn/post/7185014176501727288

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。

在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集
在这里插入图片描述
二、源码解析合集

在这里插入图片描述
三、开源框架合集

在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

猜你喜欢

转载自blog.csdn.net/datian1234/article/details/128569502