-
Notifications
You must be signed in to change notification settings - Fork 518
Flutter绘制基础
有两个类做这件事情:
-
CustomPaint
:会在绘制阶段提供一个 Canvas 画布 -
CustomPainter
: 具体的画笔, 可配置画笔的颜色,路径等
构造方法:
const CustomPaint({
Key key,
this.painter,
this.foregroundPainter,
this.size = Size.zero,
this.isComplex = false,
this.willChange = false,
Widget child,
}) :super(key: key, child: child);
我们只需要关心三个参数,painter,foregroundPainter 和 child , 这里需要说明一下,painter 是绘制的 backgroud 层,而child 是在backgroud之上绘制,foregroundPainter 是在 child 之上绘制,所以这里就有了个层级关系,这跟android里面的backgroud与foreground是一个意思,那这两个painter的应用场景是什么呢?假如你只是单纯的想绘制一个图形,只用painter就可以了,但是如果你想给绘制区域添加一个背景(颜色,图片,等等),这时候如果使用 painter是会有问题的,painter的绘制会被child 层覆盖掉,此时你只需要将painter替换成foregroundPainter,然会颜色或者图片传递给child即可。
如果是Android绘制几何图形,应该是重写View的onLayout() 和 onDraw方法,但是Flutter实现绘制,必须继承CustomPainter并重写 paint(Canvascanvas, Size size)和 shouldRepaint (CustomPainteroldDelegate) 方法 ,第一个参数canvas就是我们绘制的画布了(跟Android一模一样),paint第二个参数Size就是上面CustomPaint构造方法传入的size, 决定绘制区域的宽高信息
Flutter 中实现绘制的主要是CustomPainter类、
我们一般继承这个类,来使用它;
class MyPainter extends CustomPainter{
@override
void paint(Canvas canvas, Size size) {
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
然后放在父控件的child里用CustomPaint包裹
child: CustomPaint(
size: Size(200,200),
painter: MyPainter())
Canvas.drawXXX() 系列方法和 Paint 的基础掌握了,就能够进行简单的绘制需求。
Canvas 类下的所有 draw- 开头的方法,例如 drawCircle() drawArc()
方法 | 功能 |
---|---|
drawLine() | 画直线 |
drawCricle() | 画圆 |
drawOval() | 画椭圆 |
drawRect() | 画矩形 |
drawPoints() | 画点 |
drawArc() | 画圆弧 |
属性名 | 类型 | 参考值 | 功能 |
---|---|---|---|
color | Colors | Colors.blueAccent | 画笔颜色 |
strokeCap | StrokeCap | StrokeCap.round 画笔笔触类型 | |
isAntiAlias | bool | true | 是否启动抗锯齿 |
blendMode | BlendMode | BlendMode.exclusion | 颜色混合模式【官网可查】 |
style | PaintingStyle | PaintingStyle.fill | 绘画样式,默认为填充 |
colorFilter | ColorFilter | ColorFilter.mode(Colors.blueAccent,BlendMode.exclusion) | 颜色渲染模式,一般是矩阵效果来改变的,Flutter只能使用颜色混合模式 |
maskFilter | MaskFilter | MaskFilter.blur(BlurStyle.inner,3.0) | 模糊遮罩效果,Flutter只有这个 |
filterQuality | FilterQuality | FilterQuality.high | 颜色渲染模式的质量 |
stokeWidth | double | 16.0 | 画笔的粗细 |
Paint _paint = Paint()
..color = Colors.deepOrange//画笔颜色
..strokeCap = StrokeCap.round //画笔笔头类型
..isAntiAlias = true //是否开启抗锯齿
..blendMode = BlendMode.src//颜色混合模式
..style = PaintingStyle.fill //画笔样式,默认为填充
..colorFilter = ColorFilter.mode(Colors.blueAccent,
BlendMode.src) //颜色渲染模式
..maskFilter = MaskFilter.blur(BlurStyle.inner, 3.0) //模糊遮罩效果
..filterQuality = FilterQuality.high //颜色渲染模式的质量
..strokeWidth = 5.0; //画笔的宽度复制代码
好了,基础介绍完了,大家可以直接点击下方,打开Flutter官方文档查看原汁原味的资料哦
1.Canvas
2.Paint
这个方法一般用于画板底色填充及蒙版(使用蒙版时要注意图片混合模式设为BlendMode.srcOver)
canvas.drawColor(Color.fromARGB(80, 255, 0, 0), BlendMode.srcOver);
第一个参数是点的模式,分三种
- PointMode.points
- PointMode.lines
- PointMode.polygon
第二个参数是一个点的集合,第三个参数...emmm,好了话不多说,上图
List<Offset> points = new List();
points.add(new Offset(100, 100));
points.add(new Offset(125, 200));
points.add(new Offset(50, 150));
points.add(new Offset(150, 150));
points.add(new Offset(75, 200));
canvas.drawPoints(PointMode.points, points, _paint);
canvas.drawPoints(PointMode.points, points, _paint);
canvas.drawPoints(PointMode.polygon, points, _paint);
p1线的起点,p2线的终点
canvas.drawLine(Offset(100, 100), Offset(200, 200), _paint);
Flutter并没有提供画多条线的方法,只能多写几次 drawLine() 或者使用 drawPoints() 的情侣模式
第一个参数仍然是一块矩形区域,第二个参数
startAngle
与第三个参数sweepAngle
需要注意是弧度制,要转化一下 乘以个(pi / 180.0)
就行了度(下图中红线是 0 度的位置;顺时针为正角度,逆时针为负角度),第四个参数useCenter
代表是否与圆心连接,
canvas.drawArc(new Rect.fromLTWH(50, 50, 100, 50), 0.0 * (pi / 180.0), 90 * (pi / 180.0), false, _paint);
canvas.drawArc(new Rect.fromLTWH(50, 50, 100, 50), 200.0* (pi / 180.0), 90 * (pi / 180.0), true, _paint);
_paint.style = PaintingStyle.stroke; // 画线模式
canvas.drawArc(new Rect.fromLTWH(50, 50, 100, 50), 100.0* (pi / 180.0), 90 * (pi / 180.0), false, _paint);
以上就是 Canvas 所有的简单图形的绘制。除了简单图形的绘制, Canvas 还可以使用
drawPath(Path path, Paint paint)
来绘制自定义图形。
- 弧度
根据定义,一周的弧度数为2πr/r=2π,360°角=2π弧度,因此,1弧度约为57.3°,即57°17’44.806’’,1°为π/180弧度,近似值为0.01745弧度,周角为2π弧度,平角(即180°角)为π弧度,直角为π/2弧度。
- 特殊弧度
度 | 弧度 |
---|---|
0° | 0 |
30° | π/6 |
45° | π/4 |
60° | π/3 |
90° | π/2 |
120° | 2π/3 |
180° | π |
270° | 3π/2 |
360° | 2π |
canvas.drawRect(new Rect.fromLTWH(10, 50, 50, 50),_paint);
_paint.style = PaintingStyle.stroke; // 中途将画笔风格设为环形
canvas.drawRect(new Rect.fromLTWH(120, 50, 50, 50),_paint);
这里涉及了Rect的创建的方式:
Rect的创建方式是多样的。包括:
- fromPoints(Offset a ,offset b): 左上和右下角坐标来确定内切矩形的大小和位置。
- fromCircle({Offset center,double radius}):圆形的中心点坐标和半径确定外切矩形的大小和位置
- fromLTRB(left,top,right,bottom): 使用矩形的上下左右的X、Y边界值来确定矩形的大小和位置
- fromLTWH(left,top,width,height): 使用矩形的左上角的x、y坐标及矩形的宽高来确定矩形的大小和位置
canvas.drawRRect(RRect.fromLTRBR(50, 50, 200, 100, Radius.circular(10.0)), _paint);
canvas.drawRRect(RRect.fromLTRBR(50, 150, 200, 250, Radius.elliptical(10.0, 30.0)), _paint);
下面这个矩形看似纵向拉伸过了,其实没有,只是圆角模式是 elliptical (椭圆)
和drawRRect类似,使用RRect确定内部、外部矩形大小及弧度,使用paint来完成绘制。 第一个参数的区域必须包括第二个参数的区域,否则无法显示;
canvas.drawDRRect(RRect.fromLTRBR(50, 50, 200, 100, Radius.circular(10.0)),
RRect.fromLTRBR(60, 60, 190, 90, Radius.circular(10.0)), _paint);
class MyPainter extends CustomPainter {
///[定义画笔]
Paint _paint = Paint()
..color = Colors.blueAccent //画笔颜色
..strokeCap = StrokeCap.round//画笔笔触类型
..isAntiAlias = true //是否启动抗锯齿
..style = PaintingStyle.stroke //绘画风格,默认为填充
..strokeWidth = 5.0; //画笔的宽度
@override
void paint(Canvas canvas, Size size) {
//绘制两个矩形
Rect rect1 = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 60.0);
Rect rect2 = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 40.0);
//分别绘制外部圆角矩形和内部的圆角矩形
RRect outer = RRect.fromRectAndRadius(rect1, Radius.circular(10.0));
RRect inner = RRect.fromRectAndRadius(rect2, Radius.circular(10.0));
canvas.drawDRRect(outer, inner, _paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return null;
}
}
@override
void paint(Canvas canvas, Size size) {
//绘制两个矩形
Rect rect1 = Rect.fromCircle(center: Offset(150.0, 40.0), radius: 60.0);
Rect rect2 = Rect.fromCircle(center: Offset(150.0, 40.0), radius: 40.0);
//分别绘制外部圆角矩形和内部的圆角矩形
RRect outer = RRect.fromRectAndRadius(rect1, Radius.circular(30.0));
RRect inner = RRect.fromRectAndRadius(rect2, Radius.circular(10.0));
canvas.drawDRRect(outer, inner, _paint);
}
第一个参数 c 是圆心点的坐标,直接new一个Offset出来填入x,y坐标就完事了,第二个参 radius 是圆的半径,paint不必多说;
canvas.drawCircle(new Offset(200, 200), 100, _paint);
坐标系是以控件左上角开始的,跟初中学的坐标系可不一样;
这个方法和drawRect()是一毛一样的,只不过一个画的是矩形,一个是椭圆
canvas.drawOval(Rect.fromLTWH(50, 50, 100, 50), _paint);
_paint.style = PaintingStyle.stroke;
canvas.drawOval(Rect.fromLTWH(170, 50, 100, 50), _paint);
这个方法通过描述路径的方式来绘制图形,用法大概是这样:
Path _path = Path();
@override
void paint(Canvas canvas, Size size) {
_paint.style = PaintingStyle.stroke; // 画线模式
_path.addArc(new Rect.fromLTWH(50, 50, 50, 50), 135.0 * (pi / 180.0), 225.0 * (pi / 180.0));
_path.addArc(new Rect.fromLTWH(100, 50, 50, 50), 180.0 * (pi / 180.0), 225.0 * (pi / 180.0));
_path.lineTo(100, 140);
_path.lineTo(58, 93);
canvas.drawPath(_path, _paint);
}
主要方法 | 功能 |
---|---|
moveTo | 将路径起始点移动到指定的位置 |
lineTo | 从当前位置连接到指定点 |
arcTo | 曲线 |
conicTo | 贝济埃曲线 |
close | 关闭路径,连接路径的起始点 |
直接描述路径的方法还可以细分为两组:添加子图形和画线(直线或曲线)
- addXXX() - 添加子图形(由于此类方法参数与上面介绍的画简单图形一样,就不多赘述了)
-
addArc(Rect oval, double startAngle, double sweepAngle)
- 添加圆弧 -
addOval(Rect oval)
- 添加圆 -
addPolygon(List<Offset> points, bool close)
- 添加一个由点的集合描述的多边形 -
addRect(Rect rect)
- 添加矩形 -
addRRect(Rect rect)
- 添加圆角矩形 -
addPath(Path path, Offset offset)
- 添加子路径
- XXXTo() - 画线(直线或曲线)
从当前位置向目标位置画一条直线, x 和 y 是目标位置的坐标。这两个方法的区 别是,
lineTo(x, y)
的参数是绝对坐标,而relativeLineTo(x, y)
的参数是相对当前位置的相对坐标 ;
_paint.style = PaintingStyle.stroke; // 画线模式
_path.lineTo(100, 100); // 由当前位置 (0, 0) 向 (100, 100) 画一条直线
_path.relativeLineTo(100, 0); // 由当前位置 (100, 100) 向正右方画100像素的位置
canvas.drawPath(_path, _paint);
画二阶贝塞尔曲线 - quadraticBezierTo(double x1, double y1, double x2, double y2) / relativeQuadraticBezierTo(double x1, double y1, double x2, double y2)
x1,y1是控制点的坐标;x2,y2是结束点的坐标;relativeQuadraticBezierTo()同上面相对直线方法
_paint.style = PaintingStyle.stroke; // 画线模式
List<Offset> points = new List();
points.add(new Offset(100, 50)); // 画出控制点位置,方便理解
canvas.drawPoints(PointMode.points, points, _paint);
_path.moveTo(0, 100); // 移动起点到(0,100)
_path.quadraticBezierTo(100, 50, 200, 100);
canvas.drawPath(_path, _paint);
画三阶贝塞尔曲线 - cubicTo(double x1, double y1, double x2, double y2, double x3, double y3) / relativeCubicTo(double x1, double y1, double x2, double y2, double x3, double y3)
和上面这个
quadraticBezierTo()
和relativeQuadraticBezierTo()
的二阶贝塞尔曲线同理,就不多说了。
不论是直线还是贝塞尔曲线,都是以当前位置作为起点,而不能指定起点。但可以通过 moveTo(x, y) 或 relativeMoveTo(x, y) 来改变当前位置,从而间接地设置这些方法的起点。
_paint.style = PaintingStyle.stroke; // 画线模式
_path.moveTo(20, 40); // 移动起点到(20,40)
_path.lineTo(80, 100); // 画条斜线
_path.moveTo(100, 40); // 移动起点到(100,20)
_path.lineTo(100, 100); // 画条直线
canvas.drawPath(_path, _paint);
但凡事都有例外
arcTo()
这个方法并不从当前位置开始绘制
前三个参数,我们已经很熟悉了,最后一个参数的意思是,画这个弧的时候是拖着笔到起点还是抬下笔到起点
拖着笔
抬着笔
_paint.style = PaintingStyle.stroke; // 画线模式
_path.moveTo(20, 40); // 移动起点到(20,20)
_path.lineTo(80, 100); // 画条斜线
_path.arcTo(new Rect.fromLTWH(60, 60, 100, 100), 0.0 * (pi / 180.0), 90.0 * (pi / 180.0), false);
_path.close(); // 封闭当前路径
canvas.drawPath(_path, _paint);
到这里Canvas图形的绘制就讲的差不多了,图形简单时,使用
drawCircle()
drawRect()
等方法来直接绘制;图形复杂时,使用drawPath()
来绘制自定义图形。 除此之外,Canvas
还可以绘制图片和文字。
画图片 - drawImage(Image image, Offset p, Paint paint) / drawImageRect(Image image, Rect src, Rect dst, Paint paint)
drawImage() 从指定点开始将图片宽高按像素绘制,由于无法控制图片的大小,并不常用;
第一个参数是**
ui
包下的Image
**,并不是**Image Widget**
;
Image 可以通过以下代码获取
ui.Image image;
/**
* 初始化图片
*
Future<VoidCallback> initImage() async {
image = await _loadImage("./assets/images/img.jpg");
return null;
}
/**
* 通过assets路径,获取资源图片
*/
Future<Image> _loadImage(String assets) async {
final ByteData data = await rootBundle.load(assets);
if (data == null) throw 'Unable to read data';
Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
FrameInfo frame = await codec.getNextFrame();
return frame.image;
}
然后在initState() 方法中初始化( shouldRepaint() 方法一定要记得返回 true ,否则无法重绘)
void initState() {
super.initState();
painter.initImage().then((val) {
setState(() {
});
});
}
最后在 paint() 方法中填入以下代码:
canvas.drawImage(image, new Offset(0, 0), _paint);
drawImageRect() 这个方法经常使用;主要了解第二个参数与第三个参数:
- Rect src - 原图的区域,一般传图片的宽高
- Rect dst - 显示的区域, 指图片显示的区域,如果原图区域宽高比与显示区域不一致,原图会被拉伸或压缩
canvas.drawImageRect(image, Offset(0.0, 0.0) & Size(image.width.toDouble(), image.height.toDouble()), Offset(0.0, 0.0) & Size(200, 200), _paint);
正常比例:
拉伸:
代码注释的很清楚,这里循环画了5段文字
for (int i = 0; i<5 ;i++){
// 新建一个段落建造器,然后将文字基本信息填入;
ParagraphBuilder pb = ParagraphBuilder(ParagraphStyle(
textAlign: TextAlign.left,
fontWeight: FontWeight.w300,
fontStyle: FontStyle.normal,
fontSize: 15.0+i,
));
pb.pushStyle(ui.TextStyle(color: Colors.black87));
pb.addText('Flutter一统移动端');
// 设置文本的宽度约束
ParagraphConstraints pc = ParagraphConstraints(width: 300);
// 这里需要先layout,将宽度约束填入,否则无法绘制
Paragraph paragraph = pb.build()..layout(pc);
// 文字左上角起始点
Offset offset = Offset(50, 50+i*40.0);
canvas.drawParagraph(paragraph, offset);
}
Canvas及paint的使用基础部分大概就是这样了