flutter的画布认识
Posted flutter开发精选
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了flutter的画布认识相关的知识,希望对你有一定的参考价值。
本节目标:
[1]. 认识画布的变换和状态 (save/restore)。
[2]. 基础图形的绘制操作:绘制点、绘制线、绘制类矩形、绘制类圆。
[3]. 其他绘制:绘制颜色、绘制画笔、绘制阴影、绘制路径。
[4]. 画布裁剪:矩形裁剪、圆角矩形裁剪、路径裁剪。
一、画布变换和状态
画布变换主要通过一个
4*4
的变换矩阵。其中transform方法是最核心的
,也是最难用的。
不过另外四个方法是为了简便使用,对 transform 的封装。
注意: 画布的变换是持久性的,变换之后所有的绘制会在变换后的画布上进行。
变换不是永久性的变换,需要使用状态的存储【save】和恢复【restore】回到之前的画布状态。
1.平移变换:
如果想要屏幕的 (0,0) 点
永久在屏幕中心
,可以将画布进行偏移
这样之后的绘制就会以中心为原点。
---->[p03_canvas/01_operation_translate/paper.dart]----
@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);
canvas.drawLine(
Offset(20, 20),
Offset(50, 50),
paint
..color = Colors.red
..strokeWidth = 5
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke);
}
2.缩放变换
【目标】:
现在通过变换实现一个圆点在中心的网格
[1]. 练习平移操作: 通过线的平移绘制出右下角四分之一网格线
[2]. 练习缩放操作: 通过缩放四分之一网格线,绘制出另外四分之三网格线
[3]. 了解画布的存储【save】和恢复【restore】用法
---->[p03_canvas/02_operation_scale_grid/paper.dart]----
class PaperPainter extends CustomPainter {
Paint _gridPint; // 画笔
final double step = 20; // 小格边长
final double strokeWidth = .5; // 线宽
final Color color = Colors.grey; // 线颜色
PaperPainter() {
_gridPint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..color = color;
}
实现方式是画一条直线,然后通过画板的平移,进行画线。
如下代码中,绘制横线时使用的点位是都是Offset(0, 0), Offset(size.width / 2, 0)
只是在每次画完后,将画布向下移step
距离,就相当于在纸上画线,你的手位置不变,而是纸在动。这样的好处是只需要做一个动作即可
,比如打印机是绘制者,打印过程中打印机不会动,动的是纸。
在很多情况下,将画布进行移动可以避免很多计算
过程,让绘制的逻辑
更加清晰
和简单
。
---->[p03_canvas/02_operation_scale_grid/paper.dart]----
void _drawBottomRight(Canvas canvas, Size size) {
canvas.save();
for (int i = 0; i < size.height / 2 / step; i++) {
canvas.drawLine(Offset(0, 0), Offset(size.width / 2, 0), _gridPint);
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.save() 时,当前画布的状态就会被保存,当执行 canvas.restore() 时,画布就会回到上次保存的状态。
比如:在上面画横线前save画布这时画布的[顶点在屏幕中心],画横线的过程中画布的顶点被[下移到了最后]。
画完后restore画布,就能让画布顶点重新回到[屏幕中心]。
现在已经画完四分之一了,也许你正想傻傻再画出其他三个。有更巧妙的方法:
如果是相同或者对称的对象,可以通过缩放进行对称变化。
沿x轴镜像,就相当于canvas.scale(1, \-1)
;
沿y轴镜像,就相当于canvas.scale(-1, 1)
;
沿原点镜像,就相当于canvas.scale(-1, \-1)
;
---->[p03_canvas/02_operation_scale_grid/paper.dart]----
void _drawGrid(Canvas canvas, Size size) {
_drawBottomRight(canvas, size);
canvas.save();
canvas.scale(1, -1);//沿x轴镜像
_drawBottomRight(canvas, size);
canvas.restore();
canvas.save();
canvas.scale(-1, 1);//沿y轴镜像
_drawBottomRight(canvas, size);
canvas.restore();
canvas.save();
canvas.scale(-1, -1);//沿原点镜像
_drawBottomRight(canvas, size);
canvas.restore();
}
3.旋转变换:
如下,通过旋转画布可以画出一圈的小线,不过不要画布旋转,而是计算点位,那就会相当痛苦。
遍历 12 次,每次将画布旋转2 * pi / count
弧度,这样就可以绘制一圈的小线。
---->[p03_canvas/03_operation_rotate/paper.dart]----
void _drawDot(Canvas canvas, Paint paint) {
final int count = 12;
paint
..color = Colors.orangeAccent
..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();
}
二、基础图形绘制:
1. 点绘制 : drawPoints、drawRawPoints
【1】 绘点: drawPoints
绘制点需要传入
点模式pointMode
、一个Offset
的列表和画笔。
使用下面的一组点进行绘点测试:本节源码p03_canvas/04_point_line/paper.dart
---->[p03_canvas/04_point_line/paper.dart]----
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),
];
-
PointMode.points : 点模式
点模式下就是将 Offset 列表的每个点依次绘出。
void _drawPointsWithPoints(Canvas canvas) {
_paint
..color = Colors.red
..style = PaintingStyle.stroke..strokeWidth=10
..strokeCap = StrokeCap.round;
canvas.drawPoints(PointMode.points, points, _paint);
}
-
PointMode.lines : 线段模式
线段模式下:每两个点一对形成线段。如果点是奇数个,那么最后一个点将没有用。
void _drawPointsWithLines(Canvas canvas) {
_paint
..color = Colors.red
..style = PaintingStyle.stroke
..strokeWidth = 1
..strokeCap = StrokeCap.round;
canvas.drawPoints(PointMode.lines, points, _paint);
}
-
PointMode.polygon : 多边形连线模式
多边形连线模式下:所有的点依次连接成图形。
void _drawPointLineWithPolygon(Canvas canvas) {
_paint
..color = Colors.red
..style = PaintingStyle.stroke
..strokeWidth = 1
..strokeCap = StrokeCap.round;
canvas.drawPoints(PointMode.polygon, points, _paint);
}
【2】 绘点集: drawRawPoints
通过
Float32List
得到点数据信息,点绘制模式同上。
void _drawRawPoints(Canvas canvas) {
Float32List pos = Float32List.fromList([
-120, -20,-80, -80,-40,
-40,0, -100,40, -140,
80, -160,120, -100]);
_paint
..color = Colors.red
..style = PaintingStyle.stroke
..strokeWidth = 10
..strokeCap = StrokeCap.round;
canvas.drawRawPoints(PointMode.points, pos, _paint);
}
2. 绘制线 : drawLine
指定两点绘制一条线,如下的
两个蓝色坐标轴
由六条线构成(包括两个尖角的线)。
将上面的点绘制效果保留,会呈现如下的折线图:
void _drawAxis(Canvas canvas, Size size) {
_paint..color=Colors.blue..strokeWidth=1.5;
canvas.drawLine(Offset(-size.width/2, 0) , Offset(size.width/2, 0),_paint);
canvas.drawLine(Offset( 0,-size.height/2) , Offset( 0,size.height/2),_paint);
canvas.drawLine(Offset( 0,size.height/2) , Offset( 0-7.0,size.height/2-10),_paint);
canvas.drawLine(Offset( 0,size.height/2) , Offset( 0+7.0,size.height/2-10),_paint);
canvas.drawLine(Offset(size.width/2, 0) , Offset(size.width/2-10, 7),_paint);
canvas.drawLine(Offset(size.width/2, 0) , Offset(size.width/2-10, -7),_paint);
}
3.类矩形绘制:drawRect、drawRRect、drawDRRect
矩形的绘制是非常常用的操作,这里比较重要的是
矩形的五种构造方法
。
你可以根据不同的场景选用不同的构造方法,有时可以让计算变的简单。
下面是本节要绘制的内容,源码位置:p03_canvas/05_like_rect/paper.dart
【1】 绘制矩形 drawRect
下面是矩形的
五种构造方法
,当需要构造矩形时,可以选择合适的方法方便构造。
void _drawRect(Canvas canvas){
_paint..color=Colors.blue..strokeWidth=1.5;
//【1】.矩形中心构造
Rect rectFromCenter = Rect.fromCenter(center: Offset(0, 0),width: 160,height: 160);
canvas.drawRect(rectFromCenter, _paint);
//【2】.矩形左上右下构造
Rect rectFromLTRB = Rect.fromLTRB(-120, -120, -80, -80);
canvas.drawRect(rectFromLTRB, _paint..color=Colors.red);
//【3】. 矩形左上宽高构造
Rect rectFromLTWH = Rect.fromLTWH(80, -120, 40, 40);
canvas.drawRect(rectFromLTWH, _paint..color=Colors.orange);
//【4】. 矩形内切圆构造
Rect rectFromCircle = Rect.fromCircle(center: Offset(100, 100),radius: 20);
canvas.drawRect(rectFromCircle, _paint..color=Colors.green);
//【5】. 矩形两点构造
Rect rectFromPoints= Rect.fromPoints(Offset(-120 , 80),Offset(-80 , 120));
canvas.drawRect(rectFromPoints, _paint..color=Colors.purple);
}
【2】 绘制圆角矩形 drawRRect
圆角矩形可以通过一个
矩形域 Rect
和一个圆角对象 Radius
构成
6 个构造方法因地制宜,圆角是一个四分之一椭圆
,其中 x,y 表示两个半轴,控制椭圆的宽扁。四个边角的圆角样式可以独立设置
。
void _drawRRect(Canvas canvas) {
_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), _paint);
//【2】.圆角矩形fromLTRBXY构造
canvas.drawRRect(RRect.fromLTRBXY(-120, -120, -80, -80, 10, 10),
_paint..color = Colors.red);
//【3】. 圆角矩形fromLTRBR构造
canvas.drawRRect(RRect.fromLTRBR(80, -120, 120, -80, Radius.circular(10)),
_paint..color = Colors.orange);
//【4】. 圆角矩形fromLTRBAndCorners构造
canvas.drawRRect(
RRect.fromLTRBAndCorners(80, 80, 120, 120,
bottomRight: Radius.elliptical(10, 10)),
_paint..color = Colors.green);
//【5】. 矩形两点构造
Rect rectFromPoints = Rect.fromPoints(Offset(-120, 80), Offset(-80, 120));
canvas.drawRRect(
RRect.fromRectAndCorners(rectFromPoints,
bottomLeft: Radius.elliptical(10, 10)),
_paint..color = Colors.purple);
}
【3】 绘制两个圆角矩形差域 drawDRRect
核心是找到两个圆角矩形的区域,前者减去后者。
后者的区域必须小于前者
void _drawDRRect(Canvas canvas) {
_paint
..color = Colors.blue
..strokeWidth = 1.5;
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), _paint);
Rect outRect2 =
Rect.fromCenter(center: Offset(0, 0), width: 60, height: 60);
Rect inRect2 =
Rect.fromCenter(center: Offset(0, 0), width: 40, height: 40);
canvas.drawDRRect(RRect.fromRectXY(outRect2, 15, 15),
RRect.fromRectXY(inRect2, 10, 10), _paint..color=Colors.green);
}
4. 绘制类圆 drawCircle,drawOval,drawArc
类圆主要有
圆、椭圆、圆弧
,圆是一个中心点 Offset 和半径
组成,椭圆的形状由一个矩形域
确定。
---->[p03_canvas/06_like_circle/paper.dart]----
void _drawFill(Canvas canvas) {
canvas.save();
canvas.translate(-200, 0);
canvas.drawCircle(Offset(0, 0), 60, _paint);
canvas.restore();
var rect = Rect.fromCenter(center: Offset(0, 0), height: 100, width: 120);
canvas.drawOval(rect, _paint);
canvas.save();
canvas.translate(200, 0);
//drawArc(矩形区域,起始弧度,扫描弧度,是否连中心,画笔)
canvas.drawArc(rect, 0, pi / 2 * 3, true, _paint);
canvas.restore();
}
drawArc 的第四参,表示是否使用中心点,下左图为 false,表示两端不与中间连线。
中间图为 true,表示两端与中间连线。
下图代码详见: p03_canvas/06_like_circle/paper.dart#_drawArcDetail
三、其他绘制
1. 绘制颜色 drawColor
左侧是原图,在此基础上绘制颜色,需要传入
颜色
和混合模式
,
如下使用蓝色的 BlendMode.lighten
,结果为右图。注:混合模式
将在[Color篇]
详解
原图 |
lighten blue |
---|---|
---->[p03_canvas/07_color/paper.dart]----
canvas.drawColor(Colors.blue, BlendMode.lighten);
2.绘制画笔drawPaint
直接使用画笔填充画布。你可以为画笔设置
滤镜
或着色器
、混色模式
后,进行绘制一些特效。
比如下面的七彩水平渐变坐标系
---->[p03_canvas/08_paint/paper.dart]----
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);
3.绘制阴影drawShadow
阴影是根据路径绘制的,
drawShadow
有四个参数:路径 path
、颜色 color
、影深 elevation
和内部是否显示 transparentOccluder
---->[p03_canvas/09_shadow/paper.dart]----
Path path = Path();
path.lineTo(80, 80);
path.lineTo(-80, 80);
path.close();
canvas.drawShadow(path, Colors.red, 3, true);
canvas.translate(200, 0);
canvas.drawShadow(path, Colors.red, 3, false);
4. 绘线路径drawPath
drawPath
是一个极其重要的 API,为绘制提供了无限可能。主要用于将一个路径绘制出来。
---->[p03_canvas/10_path/paper.dart]----
Path path = Path();
path.lineTo(60, 60);
path.lineTo(-60, 60);
path.lineTo(60, -60);
path.lineTo(-60, -60);
path.close();
canvas.drawPath(path, _paint);
canvas.translate(140, 0);
canvas.drawPath(
path,
_paint
..style = PaintingStyle.stroke
..strokeWidth = 2);
四、画布的裁剪:
1、矩形裁剪:
指定一个矩形,画布
在之后的
绘制中仅保留矩形内的内容
。可通过save/restore
存换状态。
下面是在裁剪
后进行渐变色的绘制,可见,只在矩形域内生效。另外有两个可选属性:
doAntiAlias:是否抗锯齿 默认true
clipOp:ClipOp.intersect 裁内部(默认) ClipOp.difference 裁外部
---->[p03_canvas/11_clip_rect/paper.dart]----
var rect = Rect.fromCenter(center: Offset.zero,width: 360,height: 240);
canvas.clipRect(rect,doAntiAlias: true,clipOp: ui.ClipOp.intersect); // 裁剪画布
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(
rect.centerLeft, rect.centerRight,
colors, pos, TileMode.clamp);
_paint.blendMode=BlendMode.lighten;
canvas.drawPaint(_paint);
2、圆角矩形裁剪:
和矩形裁剪基本一致,只不过效果作用于:
一个圆角矩形区域
---->[p03_canvas/12_clip_rrect/paper.dart]----
var rect = Rect.fromCenter(center: Offset.zero,width: 200,height: 100);
canvas.clipRRect(RRect.fromRectAndRadius(rect, Radius.circular(30)));
canvas.drawColor(Colors.red, BlendMode.darken);
3、路径裁剪:
和矩形裁剪基本一致,只不过效果作用于:
一个指定路径的区域
下面使用一个三角形的路径裁剪画布,drawColor
就会只作用于当前区域内
---->[p03_canvas/13_clip_path/paper.dart]----
Path path = Path();
path.lineTo(80, 80);
path.lineTo(-80, 80);
path.close();
canvas.clipPath(path);
canvas.drawColor(Colors.red, BlendMode.darken);
现在我们已经对画布的基本操作和基础形状绘制了解完毕,接下来看一下画布对
图像
和文字
的绘制支持。
以上是关于flutter的画布认识的主要内容,如果未能解决你的问题,请参考以下文章
错误记录Flutter 混合开发获取 BinaryMessenger 报错 ( FlutterActivityAndFragmentDelegate.getFlutterEngine() )(代码片段
在 webview_flutter 中启用捏合和缩放,在哪里添加代码片段 [this.webView.getSettings().setBuiltInZoomControls(true);]