在flutter里面有着万物皆是widget这句话,CustomPaint是Flutter中用于自由绘制的一个widget。它与android原生的绘制规则基本一致,以当前Canves(画布)的左上角为原点进行绘制。
构造方法:
class CustomPaint extends SingleChildRenderObjectWidget {
/// Creates a widget that delegates its painting.
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),
assert(painter != null || foregroundPainter != null || (!isComplex && !willChange)),
super(key: key, child: child);
/// The painter that paints before the children.
final CustomPainter? painter;
/// The painter that paints after the children.
final CustomPainter? foregroundPainter;
/// The size that this [CustomPaint] should aim for, given the layout
/// constraints, if there is no child.
///
/// Defaults to [Size.zero].
///
/// If there's a child, this is ignored, and the size of the child is used
/// instead.
final Size size;
/// Whether the painting is complex enough to benefit from caching.
///
/// The compositor contains a raster cache that holds bitmaps of layers in
/// order to avoid the cost of repeatedly rendering those layers on each
/// frame. If this flag is not set, then the compositor will apply its own
/// heuristics to decide whether the this layer is complex enough to benefit
/// from caching.
///
/// This flag can't be set to true if both [painter] and [foregroundPainter]
/// are null because this flag will be ignored in such case.
final bool isComplex;
/// Whether the raster cache should be told that this painting is likely
/// to change in the next frame.
///
/// This flag can't be set to true if both [painter] and [foregroundPainter]
/// are null because this flag will be ignored in such case.
final bool willChange;
这个页面除去AppBar,下面整个都是CustomPaint的范围。
要实现绘制,需传入painter参数,定义自己的painter继承自CustomPainter,这里面得必须重写paint和shouldRepaint。但实现绘制仅需实现paint方法
paint的两个参数 canvas 和 size ,分别是画布和CustomPaint的子widget的宽高尺寸。
接着仅需cavas的 draw操作就可以了。
class MyPaint extends StatelessWidget {
const MyPaint({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: getAppBar('CustomPaint'),
body: CustomPaint(
painter: MyPainer(),
),
);
}
}
class MyPainer extends CustomPainter{
late Paint _paint;
@override
void paint(Canvas canvas, Size size) {
_paint = Paint();
_paint.color = Colors.red;
canvas.drawCircle(Offset(100, 100), 100, _paint);
canvas.drawLine(Offset(300, 300), Offset(400, 400), _paint);
// TODO: implement paint
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
throw UnimplementedError();
}
}
CustomPaint的构造函数
child: 红色区域,传入一个子widget,这个widget图层会在painter在上,在foregroundPainter之下。
painter:蓝色区域。
foregroundPainter:绿色区域,它与painter都是CustomPainter类型的。通过名字大概也就知道了,它会在painter的上层,也就是说在同样的位置去绘制,foregroundPainter 会覆盖painter。
完整代码:
import 'package:demo202112/utils/common_appbar.dart';
import "package:flutter/material.dart";
class MyPaint extends StatelessWidget {
const MyPaint({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: getAppBar('CustomPaint'),
body: CustomPaint(
painter: MyPainer(),
child: Container(height: 80,width: 80,child: Text('child测试'),color: Colors.red,),
foregroundPainter: MyForeGroundPainer(),
),
);
}
}
class MyPainer extends CustomPainter{
late Paint _paint;
@override
void paint(Canvas canvas, Size size) {
_paint = Paint();
_paint.color = Colors.blue;
canvas.drawCircle(Offset(100, 100), 100, _paint);
canvas.drawLine(Offset(300, 300), Offset(400, 400), _paint);
// TODO: implement paint
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
throw UnimplementedError();
}
}
class MyForeGroundPainer extends CustomPainter{
late Paint _paint;
@override
void paint(Canvas canvas, Size size) {
_paint = Paint();
_paint.color = Colors.green;
canvas.drawCircle(Offset(100, 100), 70, _paint);
// canvas.drawLine(Offset(300, 300), Offset(400, 400), _paint);
// TODO: implement paint
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
throw UnimplementedError();
}
}
isComplex: 绘制是否足够复杂需要缓存,作用很明显就是为了避免在每个图层上重复渲染。
isChange:下一帧中绘制是否更改。
size:默认值是size.zero(宽高都是0),可以理解为这个就是child的宽高,但是有child后以,这个参数被child的尺寸代替。这里传入的size在pain方法里可以获取到。源码里面用这句话解释“The size that this [CustomPaint] should aim for, given the layout”,但就我测试情况看,无论如何更改size,绘制结果不受任何影响。
class MyPoints extends CustomPainter{
Paint _paint = Paint()
..color = Colors.red
..strokeWidth = 15;
@override
void paint(Canvas canvas, Size size) {
// TODO: implement paint
var points =[
Offset(0, 0),
Offset(size.width/2, size.height/2),
Offset(size.width, size.height),
];
canvas.drawPoints(PointMode.points, points, _paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
throw UnimplementedError();
}
}
PointMode
有3种模式:
points:点
lines:将2个点绘制为线段,如果点的个数为奇数,最后一个点将会被忽略
polygon:将整个点绘制为一条线
class MyGraph extends CustomPainter{
final Paint _paint = Paint()
..color = Colors.red
..strokeWidth = 15;
final Paint _paintPath = Paint()
..color = Colors.blue
..strokeWidth = 5
..style = PaintingStyle.fill;
@override
void paint(Canvas canvas, Size size) {
// TODO: implement paint
//绘制线
canvas.drawLine(Offset(0, 30),Offset(size.width-30, size.height), _paint);
//绘制路径
var _path = Path()
..moveTo(0, 0)
..lineTo(size.width, 0)
..lineTo(size.width, size.height)
..close();
canvas.drawPath(_path, _paintPath);
//这里注意Paint.style,还可以设置为PaintingStyle.fill,
//绘制圆形
canvas.drawCircle(Offset(size.width/2+50, size.height/2+50), 20, _paint);
//绘制椭圆
canvas.drawOval(Rect.fromLTRB(0, 0, size.width, size.height/2), _paint);
//绘制弧
canvas.drawArc(Rect.fromLTRB(0, 0, size.width, size.height), 0, pi/2, true, _paint);
//绘制圆角矩形
canvas.drawRRect(RRect.fromLTRBR(0, 0, size.width, size.height, Radius.circular(10)), _paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
throw UnimplementedError();
}
}
通过CustomPaint
画出了一个蓝天跟太阳出来
class MySunPainer extends CustomPainter{
final Paint _paint = Paint();
@override
void paint(Canvas canvas, Size size) {
var rect = Offset.zero & size;
var gradient = RadialGradient(
center: const Alignment(0.7, -0.6),
radius: 0.2,
colors: [const Color(0xFFFFFF00), const Color(0xFF0099FF)],
stops: [0.4, 1.0],
);
canvas.drawRect(
rect,
Paint()..shader = gradient.createShader(rect),
);
}
SemanticsBuilderCallback get semanticsBuilder{
return (Size size) {
// Annotate a rectangle containing the picture of the sun
// with the label "Sun". When text to speech feature is enabled on the
// device, a user will be able to locate the sun on this picture by
// touch.
var rect = Offset.zero & size;
var width = size.shortestSide * 0.4;
rect = const Alignment(0.8, -0.9).inscribe(Size(width, width), rect);
return [
CustomPainterSemantics(
rect: rect,
properties: SemanticsProperties(
label: 'Sun',
textDirection: TextDirection.ltr,
),
),
];
};
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
// throw UnimplementedError();
return this != oldDelegate;
}
}