CustomPaint
class CustomPaint extends SingleChildRenderObjectWidget
const CustomPaint({
Key key,
this.painter,
this.foregroundPainter,
this.size = Size.zero,
this.isComplex = false,
this.willChange = false,
Widget child,
}) : assert(size != null),
assert(isComplex != null),
assert(willChange != null),
super(key: key, child: child);
CustomPaint继承自SingleChildRenderObjectWidget,所以不能用setState来刷新!
其中:
- painter就是我们的主绘制工具,它是一个CustomPainter,画笔角色;
- foregroundPainter是用来绘制前景的工具;
- size为画布大小,这个size会传递给Painter;
- isComplex和willChange 是告诉Flutter你的CustomPaint是否复杂到需要使用cache相关的功能;
- child属性我们一般不填,即使你是想要在你的CustomPaint上添加一些其他的布局,也不建议放在child属中性,因为你会发现你并不会得到你想要的结果。
所有的绘制都是发生在Painter里面的,绘制的代码写在我们的自定义CustomPainter中:
CustomPainter
class LinePainter extends CustomPainter {
///Flutter中负责View绘制的地方,使用传递来的canvas和size即可完成对目标View的绘制
@override
void paint(Canvas canvas, Size size) {
// 绘制直线
_drawLine(canvas);
// 绘制点
_drawPoint(canvas);
// 绘制圆
_drawCircle1(canvas);
_drawCircle2(canvas);
// 绘制椭圆drawOval
_drawOval(canvas);
//绘制圆弧drawArc
_drawArc1(canvas);
_drawArc2(canvas);
// 绘制圆角矩形drawDRRect
_drawRRect(canvas);
// 绘制双圆角矩形drawDRRect
_drawDRRect(canvas);
_drawDRRect1(canvas);
// 绘制路径drawPath
_drawPath(canvas);
}
///控制自定义View是否需要重绘的,返回false代表这个View在构建完成后不需要重绘。
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
我们需要重写paint()和shouldRepaint()这两个方法,前者是绘制流程,后者是在刷新布局的时候告诉Flutter是否需要重绘。注意下paint方法中的size参数,就是我们在CustomPaint中定义的size属性,它包含了基本的画布大小信息。
真正地绘制则是通过canvas和Paint来实现的,我们将定义好了的Paint画笔传递给canvas.drawXXX()方法
Paint
Paint _paint = Paint()
..color = Colors.blueAccent // 画笔颜色
..strokeCap = StrokeCap.round // 画笔笔触类型
..isAntiAlias = true // 是否启动抗锯齿
// ..blendMode = BlendMode.exclusion // 颜色混合模式
..style = PaintingStyle.stroke // 绘画风格,默认是填充
// ..colorFilter = ColorFilter.mode(Colors.blueAccent,
// BlendMode.exclusion) // 颜色渲染模式,一般是矩阵效果来改变的,但是flutter中只能使用颜色混合模式
// ..maskFilter = MaskFilter.blur(BlurStyle.inner, 3.0) // 模糊遮罩效果,flutter中只有这个
..filterQuality = FilterQuality.high
..strokeWidth = 5.0;
具体的一些实现
注意,Demo是以iPhone8 plus设备为例,未做全部机型适配
void _drawLine(Canvas canvas) {
// 绘制直线
Offset p1 = Offset(20, 20);
Offset p2 = Offset(120, 20);
canvas.drawLine(p1, p2, _paint);
}
void _drawPoint(Canvas canvas) {
canvas.drawPoints(
///PointMode的枚举类型有三个,
///points(点), lines(线,隔点连接), polygon(线,相邻连接)
PointMode.polygon,
[
Offset(20, 50),
Offset(120, 50),
Offset(80, 100),
Offset(80, 150),
Offset(60, 170),
Offset(60, 100),
Offset(20, 50),
],
_paint);
}
void _drawCircle1(Canvas canvas) {
// 绘制圆 参数(圆心,半径,画笔)
canvas.drawCircle(Offset(150, 80), 20, _paint);
}
void _drawCircle2(Canvas canvas) {
Paint paint = Paint()
..color = Colors.green
..strokeCap = StrokeCap.round
..isAntiAlias = true
..style = PaintingStyle.fill
..strokeWidth = 5;
;
// 绘制圆 参数(圆心,半径,画笔)
canvas.drawCircle(Offset(150, 140), 20, paint);
}
void _drawOval(Canvas canvas) {
Paint paint = Paint()
..color = Colors.red
..strokeCap = StrokeCap.round
..isAntiAlias = true
..strokeWidth = 5
..style = PaintingStyle.stroke;
/**
使用左上和右下角坐标来确定矩形的大小和位置
fromPoints(Offset a, Offset b)
使用圆的圆心点坐标和半径和确定外切矩形的大小和位置
fromCircle({ Offset center, double radius })
使用矩形左边的X坐标、矩形顶部的Y坐标、矩形右边的X坐标、矩形底部的Y坐标来确定矩形的大小和位置
fromLTRB(double left, double top, double right, double bottom)
使用矩形左边的X坐标、矩形顶部的Y坐标矩形的宽高来确定矩形的大小和位置
fromLTWH(double left, double top, double width, double height)
*/
Rect rect = Rect.fromPoints(Offset(200, 20), Offset(300, 60));
canvas.drawRect(rect, paint);
canvas.drawOval(rect, _paint);
}
void _drawArc1(Canvas canvas) {
Paint paint = Paint()
..color = Colors.orange
..strokeCap = StrokeCap.round
..strokeWidth = 5
..style = PaintingStyle.stroke
..isAntiAlias = true;
Rect rect = Rect.fromLTWH(200, 80, 100, 40);
canvas.drawRect(rect, paint);
double startAngle = 0;
double sweepAngle = PI / 2;
bool useCenter = true;
canvas.drawArc(rect, startAngle, sweepAngle, useCenter, _paint);
}
void _drawArc2(Canvas canvas) {
Paint paint = Paint()
..color = Colors.purple
..strokeCap = StrokeCap.round
..strokeWidth = 5
..style = PaintingStyle.stroke
..isAntiAlias = true;
Rect rect = Rect.fromLTWH(320, 20, 80, 80);
canvas.drawRect(rect, paint);
double startAngle = 0;
double sweepAngle = PI / 2;
bool useCenter = true;
canvas.drawArc(rect, startAngle, sweepAngle, useCenter, _paint);
}
void _drawRRect(Canvas canvas) {
Rect rect = Rect.fromCircle(center: Offset(70, 250), radius: 50);
Radius radius = Radius.circular(20);
// Radius radius = Radius.circular(50); 改成50会变成一个圆
RRect rrect = RRect.fromRectAndRadius(rect, radius);
// RRect rrect = RRect.fromRectAndCorners(rect,topLeft: Radius.circular(20),bottomRight: Radius.circular(10));
canvas.drawRRect(rrect, _paint);
}
void _drawDRRect(Canvas canvas) {
Radius radius1 = Radius.circular(10);
Rect rect1 = Rect.fromCircle(center: Offset(200, 250), radius: 50);
RRect outer = RRect.fromRectAndRadius(rect1, radius1);
// canvas.drawRRect(inner, _paint);
Radius radius2 = Radius.circular(5);
Rect rect2 = Rect.fromCircle(center: Offset(200, 250), radius: 20);
RRect inner = RRect.fromRectAndRadius(rect2, radius2);
// canvas.drawRRect(outer, _paint);
canvas.drawDRRect(outer, inner, _paint); // 注意 当outer和inner位置相反时,绘制不出结果
}
void _drawDRRect1(Canvas canvas) {
Paint paint1 = Paint()
..color = Color(0xffc8a556)
..strokeCap = StrokeCap.round
..strokeWidth = 5
..style = PaintingStyle.stroke
..isAntiAlias = true;
// Paint paint2 = Paint()
// ..color = Color.fromRGBO(0, 0, 0, 1)
// ..strokeCap = StrokeCap.round
// ..strokeWidth = 5
// ..style = PaintingStyle.fill
// ..isAntiAlias = true
// ;
Radius radius1 = Radius.circular(50);
Rect rect1 = Rect.fromCircle(center: Offset(330, 250), radius: 50);
RRect outer = RRect.fromRectAndRadius(rect1, radius1);
canvas.drawRRect(outer, paint1);
Radius radius2 = Radius.circular(5);
Rect rect2 = Rect.fromCircle(center: Offset(330, 250), radius: 20);
RRect inner = RRect.fromRectAndRadius(rect2, radius2);
canvas.drawRRect(inner, paint1);
}
void _drawPath(Canvas canvas) {
/**
Path的常用方法:
将路径起始点移动到指定的位置
moveTo
相对于当前位置移动到
relativeMoveTo
从当前位置连接指定点
lineTo
相对当前位置连接到
relativeLineTo
曲线
arcTo
贝塞尔曲线
conicTo
添加其他图形,如addArc,在路径是添加圆弧
add**
路径上是否包括某点
contains
给路径做matrix4变换
transfor
结合两个路径
combine
关闭路径,连接路径的起始点
close
重置路径,恢复到默认状态
reset
*/
Path path = Path()
..moveTo(20, 320);
path.lineTo(50, 340);
path.lineTo(70, 330);
path.relativeLineTo(10, 20);
canvas.drawPath(path, _paint);
// 贝赛尔曲线
Path path1 = Path()..moveTo(90, 330);
Rect rect = Rect.fromLTWH(100, 330, 60, 60);
// 如果“forceMoveTo”参数为false,则添加一条直线段和一条弧段。
// 如果“forceMoveTo”参数为true,则启动一个新的子路径,其中包含一个弧段。
// path1.arcTo(rect, startAngle, sweepAngle, forceMoveTo)
path1.arcTo(rect, 0, PI/2, false);
canvas.drawPath(path1, _paint);
Path path2 = Path()..moveTo(150, 330);
Rect rect2 = Rect.fromLTWH(150, 330, 60, 60);
path2.arcTo(rect2, 0, PI/2, true);
canvas.drawPath(path2, _paint);
Path path3 = Path()..moveTo(250, 330);
Rect rect3 = Rect.fromLTWH(250, 330, 60, 60);
path3.arcTo(rect3, 0, 3.14 * 2, true); // PI *2/3.141592654 *2不会有绘制结果
canvas.drawPath(path3, _paint);
// 使用控制点(x1,y1)和权重w,添加从当前点到给定点(x2,y2)的曲线段。
// 如果重量大于1,那么曲线就是双曲线; 如果重量等于1,那就是抛物线; 如果小于1,则为椭圆形。
// path.conicTo(x1, y1, x2, y2, w)
}
网友评论