画布变形和状态
画布变化主要通过一个4*4的变换矩阵,其中transform方法是最核心的,也是最难用的。
不过另外的四个方法都是为了简单使用,对transform的封装
注意,画布的变换是持久性的,变换之后的所有绘制都在变换后的画布上运行。
变换不是永久性的变换,需要使用状态的存储和恢复到之前画布的状态。
1.平移变换
如果想要屏幕的(0,0)点永远在屏幕的中心,可以将画布进行偏移
但是这样之后的绘制就会以中心为原点。
@override
void paint(Canvas canvas, Size size) {
var paint = Paint()
..style = PaintingStyle.fill
..color = Colors.blue;
// 画布起点移到屏幕中心
canvas.translate(size.width/2, size.height/2);
canvas.drawCircle(Offset(0, 0), 50, paint);
}
2.缩放变换
目标:通过变换实现一个原点在中心的网络
实现方式是画一条直线,然后通过画板的平移,进行画线,如下代码中,绘制横线时使用的点位都是Offset(0,0),Offset(size.width/2,0)只是在每次画完之后,将画布往下移动step距离,就相当于在纸上画线,你的手不动,而是纸在动,这样的好处是只需要做一个动作就可以了。比如打印机是绘制者,打印过程中打印机不会动,动的是纸。
在很多情况下,将画布进行移动可以避免很多计算过程,让绘制的逻辑更加的清晰和简单。
// 通过移动画布的方式来绘制网格线
void _drawBottomRight(Canvas canvas,Size size){
// 保持画布状态
canvas.save();
// 循环绘制横向网格线
for(int i=0;i<size.height/2/step;i++){
// 画线,设置线的起点为0,终点为容器高度的1/2,用刚刚自定义好的画笔来画
canvas.drawLine(Offset(0, 0), Offset(size.width/2, 0), _gridPint);
// 设置网格往下方平移step距离
canvas.translate(0, step);
}
// 回复网格状态
canvas.restore();
// 保持当前画布状态
canvas.save();
// 于上方相同
for(int i=0;i<size.width/2/step;i++){
canvas.drawLine(Offset(0, 0), Offset(0, size.height/2), _gridPint);
canvas.translate(step, 0);
}
// 回复画布状态
canvas.restore();
}
好的,上面我们已经知道了怎么样通过canvas去画一个网格图形,并且绘制一个圆。接下来我们将会将网格铺满整个应用程序,并且在圆形外部绘制一些光线,实现效果如下:
@override
void paint(Canvas canvas, Size size) {
var paint = Paint()
..style = PaintingStyle.fill
..color = Colors.blue;
// 画布起点移到屏幕中心
canvas.translate(size.width/2, size.height/2);
canvas.drawCircle(Offset(0, 0),
50,
Paint()..color=Color(0xff0000ff));
_drawBottomRight(canvas,size);
canvas.save();
canvas.scale(1,-1);
_drawBottomRight(canvas,size);
canvas.restore();
canvas.save();
canvas.scale(-1,1);
_drawBottomRight(canvas,size);
canvas.restore();
canvas.save();
canvas.scale(-1,-1);
_drawBottomRight(canvas,size);
canvas.restore();
}
// 通过移动画布的方式来绘制网格线
void _drawBottomRight(Canvas canvas,Size size){
_drawDot(canvas, Paint());
// 保持画布状态
canvas.save();
// 循环绘制横向网格线
for(int i=0;i<size.height/2/step;i++){
// 画线,设置线的起点为0,终点为容器高度的1/2,用刚刚自定义好的画笔来画
canvas.drawLine(Offset(0, 0), Offset(size.width/2, 0), _gridPint);
// 设置网格往下方平移step距离
canvas.translate(0, step);
}
// 回复网格状态
canvas.restore();
// 保持当前画布状态
canvas.save();
// 于上方相同
for(int i=0;i<size.width/2/step;i++){
canvas.drawLine(Offset(0, 0), Offset(0, size.height/2), _gridPint);
canvas.translate(step, 0);
}
// 回复画布状态
canvas.restore();
}
void _drawDot(Canvas canvas,Paint paint){
final int count=12;
paint..color=Colors.orangeAccent
..strokeWidth=5
..style=PaintingStyle.stroke;
canvas.save();
for(int i=0;i<count;i++){
var step=2*pi/count;
canvas.drawLine(Offset(80, 0),Offset(100, 0),paint);
canvas.rotate(step);
}
canvas.restore();
}
他的主要实现原理很简单,就是通过canvas.scale这个API来翻转画布。
/// Add an axis-aligned scale to the current transform, scaling by the first
/// argument in the horizontal direction and the second in the vertical
/// direction.
///
/// If [sy] is unspecified, [sx] will be used for the scale in both
/// directions.
这个是scale源码的注释,scale传递两个参数,一个做水平翻转,一个做垂直翻转。通过阅读源码,我们就可以通过scale来对网格做重复绘制
沿X轴镜像,就相当于canvas.scale(1,-1);
沿Y轴镜像,就相当于canvas.scale(-1,1);
沿原点镜像,就相当于canvas.scale(-1,-1);
基本图形绘制
1.点绘制:drawPoints、drawRawPoints
绘制点需要传入点模式、一个Offset的列表和画笔。
使用下面的一组点进行绘点测试:
void _drawPoints(Canvas canvas){
final List<Offset> points=[
Offset(-120, -20),
Offset(-80, -80),
Offset(-40, -40),
Offset(0, -100),
Offset(40, -140),
Offset(80, -160),
Offset(120, -100),
];
canvas.drawPoints(PointMode.points, points, Paint()..style=PaintingStyle.stroke..strokeWidth=10..color=Color(0xffff0000)..strokeCap=StrokeCap.round);
}
好,这里点就给他画好了,下面我们来画线:
线的绘制其实和点的绘制一样,唯一有地方不同的就是Points的模式,
PointsMode.lines
如果说我们将模式变成polygon
那么他就会默认你给他的标量集为边标量集,他就会开始画连线
void _drawPointsWithLines(Canvas canvas){
final List<Offset> points=[
Offset(-120, -20),
Offset(-80, -80),
Offset(-40, -40),
Offset(0, -100),
Offset(40, -140),
Offset(80, -160),
Offset(120, -100),
];
canvas.drawPoints(PointMode.polygon, points, Paint()
..color=Colors.red
..style=PaintingStyle.stroke
..strokeWidth=1
..strokeCap=StrokeCap.round);
}
好,以上我们完成了折线图的绘制,下面我将会想,如果说折线图画好了,我们是不是能够定义一个坐标系呢,一个简单的二维坐标系。
canvas.drawLine(Offset(-size.width/2, 0) , Offset(size.width/2, 0),_axisPaint);
canvas.drawLine(Offset( 0,-size.height/2) , Offset( 0,size.height/2),_axisPaint);
3.类矩形绘制
矩形的绘制是非常常用的操作,这里比较重要的是矩形的5种构造方法。
你可以更具不同的场景选用不同的构造方法,有时可以让计算变得更简单
下面是矩形的5种常用的构造方法,当需要构造矩形时,可以选择合适的方法进行构造。
void _drawRect(Canvas canvas){
var rectPaint=Paint()..color=Colors.blue..strokeWidth=1.5;
// 绘制中心矩形
Rect rectFromCenter=Rect.fromCenter(center: Offset(0,0), width: 160, height: 160);
canvas.drawRect(rectFromCenter, rectPaint);
// 矩形左上右下构造
Rect rectFromLTRB=Rect.fromLTRB(-120, -120, -80, -80);
canvas.drawRect(rectFromLTRB, rectPaint..color=Colors.red);
// 矩形左上宽高
Rect rectFromLTWH=Rect.fromLTWH(70, -120, 40, 40);
canvas.drawRect(rectFromLTWH, rectPaint..color=Colors.blue);
// 矩形内切图构造
Rect rectFromCircle=Rect.fromCircle(center: Offset(100,100), radius: 20);
canvas.drawRect(rectFromCircle, rectPaint..color=Colors.black54);
// 矩形两点构造
Rect rectFromPoints=Rect.fromPoints(Offset(-100,-100), Offset(-80,-80));
canvas.drawRect(rectFromPoints, rectPaint..color=Color(0xff087966));
}
首先要说下这个矩形内切图构造,他的哪个center源码的解释是距离中心点的距离
The
centerargument is assumed to be an offset from the origin.
好了,绘制矩形没问题了,现在看绘制圆角矩形
圆角矩形可以通过一个矩形域Rect和一个圆角对象Radius构成,6个构成方法因地制宜,圆角是一个四分之一的椭圆。其中X,Y表示两个半轴,控制椭圆的宽扁,四个边的圆角样式可以独立设置。
void _drawRRect(Canvas canvas) {
var radiusPaint=Paint()
..color = Colors.blue
..strokeWidth = 1.5;
//【1】.圆角矩形fromRectXY构造
Rect rectFromCenter =
Rect.fromCenter(center: Offset(0, 0), width: 160, height: 160);
canvas.drawRRect(RRect.fromRectXY(rectFromCenter, 40, 20), radiusPaint);
//【2】.圆角矩形fromLTRBXY构造
canvas.drawRRect(RRect.fromLTRBXY(-120, -120, -80, -80, 10, 10),
radiusPaint..color = Colors.red);
//【3】. 圆角矩形fromLTRBR构造
canvas.drawRRect(RRect.fromLTRBR(80, -120, 120, -80, Radius.circular(10)),
radiusPaint..color = Colors.orange);
//【4】. 圆角矩形fromLTRBAndCorners构造
canvas.drawRRect(
RRect.fromLTRBAndCorners(80, 80, 120, 120,
bottomRight: Radius.elliptical(10, 10)),
radiusPaint..color = Colors.green);
//【5】. 矩形两点构造
Rect rectFromPoints = Rect.fromPoints(Offset(-120, 80), Offset(-80, 120));
canvas.drawRRect(
RRect.fromRectAndCorners(rectFromPoints,
bottomLeft: Radius.elliptical(10, 10)),
radiusPaint..color = Colors.purple);
}
好的,圆角矩形我们画好了,现在我们来画内部矩形:
这个在矩形的内部做操作,本质上就是大一点的外部矩形减去后者。
后者的区域必须小于前者
void _drawDRRect(Canvas canvas){
var drPaint=Paint()
..strokeWidth=1.5
..color=Colors.blue;
Rect outRect=Rect.fromCenter(center: Offset(0,0),width: 160,height: 160);
Rect inRect=Rect.fromCenter(center: Offset(0,0), width: 100, height: 100);
canvas.drawDRRect(RRect.fromRectXY(outRect, 20, 20), RRect.fromRectXY(inRect, 20, 20), drPaint);
}
好的,矩形告一段落,下面开始绘制类图
类图主要有圆、椭圆、圆弧,圆是一个中心点Offset和半径组成的,椭圆的形状由一个矩形域确定。
void _drawFill(Canvas canvas){
var fillPaint=Paint()..strokeWidth=1.5..color=Colors.green;
canvas.save();
canvas.translate(-200, 0);
canvas.drawCircle(Offset(0, 0), 60, fillPaint);
canvas.restore();
var rect=Rect.fromCenter(center: Offset(0,0), width: 100, height: 120);
canvas.drawOval(rect, fillPaint);
canvas.save();
canvas.translate(200, 0);
canvas.drawArc(rect, 0,pi/2*3,true,fillPaint);
canvas.restore();
}
1.绘制颜色
canvas.drawColor(Colors.blue,BlendMode.lighten);
2.绘制画笔
直接使用画笔来填充画布,你可以为画笔设置滤镜或者着色器、混合模式后,进行绘制一些特效。比如下面的七彩水平渐变坐标系。
var colors = [
Color(0xFFF60C0C),
Color(0xFFF3B913),
Color(0xFFE7F716),
Color(0xFF3DF30B),
Color(0xFF0DF6EF),
Color(0xFF0829FB),
Color(0xFFB709F4),
];
var pos = [1.0 / 7, 2.0 / 7, 3.0 / 7, 4.0 / 7, 5.0 / 7, 6.0 / 7, 1.0];
_paint.shader = ui.Gradient.linear(
Offset(0, 0), Offset(size.width, 0),
colors, pos, TileMode.clamp);
_paint.blendMode=BlendMode.lighten;
canvas.drawPaint(_paint);