简介
用于操作图形的界面
- canvas 对象用于创建图片对象,这些对象本身可以与 SceneBuilder 一起用于构建场景;
基本用法
- 以下是绘制点,线,面的基本用法
绘制点
- 绘制点有三种模式, points(点),lines(线,隔点连接),polygon(线,相邻连接)
绘制直线
绘制圆
绘制椭圆
- 使用左上和右下角坐标来确定矩形的大小和位置,椭圆是在这个矩形之中内切的形状
绘制圆弧
- Rect 来确认圆弧的位置,还需要开始的弧度、结束的弧度、是否使用中心点绘制、以及 paint 弧度
绘制矩形,圆角矩形
- 用 Rect构建矩形
- 根据上面的矩形,构建一个圆角矩形
绘制两个相套矩形
绘制图片到canvas
绘制一个星形
- 它可以使用“平移”、“缩放”、“旋转”、“倾斜”和“变换”方法进行修改;
- 可以使用 clipRect、clipCorrect 和 clipPath 方法进行修改;
- 可以使用由 save、savelayer和 restore 方法管理的堆栈来保存和还原当前的转换和剪辑。
实例演示
import 'package:flutter/material.dart';
import 'dart:ui';
import 'dart:ui' as ui;
import 'dart:typed_data';
import 'dart:math';
import 'package:flutter/services.dart' show rootBundle;
CustomPaint graph;
var image;
class CustomViewPage extends StatefulWidget {
final String type;
CustomViewPage({this.type='drawLine'}) : super();
@override
State<StatefulWidget> createState() => CustomViewPageState();
}
class CustomViewPageState extends State<CustomViewPage>
with SingleTickerProviderStateMixin {
static Future<ui.Image> getImage(String asset) async {
ByteData data = await rootBundle.load(asset);
Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
FrameInfo fi = await codec.getNextFrame();
return fi.image;
}
@override
void initState() {
super.initState();
getImage("assets/images/painterImg.jpeg").then((data) {
if (mounted) {
setState(() {
image = data;
});
}
});
}
@override
Widget build(BuildContext context) {
graph = CustomPaint(
painter: DrawPainter(type:widget.type)
);
return Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.width * 0.6,
margin: EdgeInsets.all(10.0),
//padding: EdgeInsets.all(10.0),
child: graph
//child:Center(child: graph)
);
}
@override
void reassemble() {
super.reassemble();
}
@override
void dispose() {
super.dispose();
}
}
///新建类继承于CustomPainter并且实现CustomPainter里面的paint()和shouldRepaint方法
class DrawPainter extends CustomPainter {
Paint painter;
final type;
DrawPainter ({this.type}){
// Paint painter = Paint()
// ..color = Colors.blueAccent //画笔颜色
// ..strokeCap = StrokeCap.round //画笔笔触类型
// ..isAntiAlias = true //是否启动抗锯齿
// ..blendMode = BlendMode.exclusion //颜色混合模式
// ..style = PaintingStyle.fill //绘画风格,默认为填充
// ..colorFilter = ColorFilter.mode(Colors.blueAccent,BlendMode.exclusion) //颜色渲染模式,一般是矩阵效果来改变的,但是flutter中只能使用颜色混合模式
// ..maskFilter = MaskFilter.blur(BlurStyle.inner, 3.0) //模糊遮罩效果,flutter中只有这个
// ..filterQuality = FilterQuality.high //颜色渲染模式的质量
// ..strokeWidth = 15.0 ;//画笔的宽度
painter = new Paint()
..color = Colors.blueAccent
..strokeCap = StrokeCap.round
..isAntiAlias = true
..strokeWidth = 5.0
..filterQuality = FilterQuality.high
..style = PaintingStyle.stroke;
}
///Flutter中负责View绘制的地方,使用传递来的canvas和size即可完成对目标View的绘制
@override
void paint(Canvas canvas, Size size) {
switch(type) {
case 'drawPoints':
const List<Offset> points1 = [Offset(20.0, 0.0), Offset(100.0, 50.0), Offset(150.0, 0.0),Offset(300.0, 0.0)];
const List<Offset> points2 = [Offset(20.0, 100.0), Offset(100.0, 100.0), Offset(150.0, 100.0), Offset(300.0, 100.0)];
const List<Offset> points3 = [Offset(20.0, 150.0), Offset(100.0, 150.0), Offset(150.0, 180.0), Offset(300.0, 150.0)];
//绘制点
canvas.drawPoints(
///PointMode的枚举类型有三个,points(点),lines(线,隔点连接),polygon(线,相邻连接)
PointMode.points,
points1,
painter..color = Colors.redAccent // 这里可以追加修改 painter 属性
..strokeWidth = 10.0
);
canvas.drawPoints(
///PointMode的枚举类型有三个,points(点),lines(线,隔点连接),polygon(线,相邻连接)
PointMode.lines,
points2,
painter..color = Colors.orange // 这里可以追加修改 painter 属性
..strokeWidth = 10.0
);
canvas.drawPoints(
///PointMode的枚举类型有三个,points(点),lines(线,隔点连接),polygon(线,相邻连接)
PointMode.polygon,
points3,
painter..color = Colors.blue // 这里可以追加修改 painter 属性
..strokeWidth = 10.0
);
break;
case 'drawLine':
//绘制直线
canvas.drawLine(Offset(20.0, 0.0), Offset(size.width*0.8, 200), painter ..color = Colors.redAccent);
break;
case 'rawCircle':
//绘制圆 参数(圆心,半径,画笔)
canvas.drawCircle(
Offset(size.width/2, 100.0),
100.0,
painter
..color = Colors.greenAccent
..style = PaintingStyle.stroke //绘画风格改为stroke
);
break;
case 'drawOval':
// 绘制椭圆
// 使用左上和右下角坐标来确定矩形的大小和位置,椭圆是在这个矩形之中内切的形状
Rect rect1 = Rect.fromPoints(Offset(0.0, 0.0), Offset(size.width, 200.0));
canvas.drawOval(rect1, painter ..color = Colors.indigo);
break;
case 'drawArc':
// 绘制圆弧
// Rect来确认圆弧的位置,还需要开始的弧度、结束的弧度、是否使用中心点绘制、以及paint弧度
const PI = 3.1415926;
Rect rect1 = Rect.fromCircle(center: Offset(20, 50.0), radius: 100.0);
canvas.drawArc(rect1, 0.0, PI / 2, false, painter ..color = Colors.pink);
Rect rect2 = Rect.fromCircle(center: Offset(size.width*0.6, 50.0), radius: 100.0);
canvas.drawArc(rect2, 0.0, PI / 2, true, painter ..color = Colors.deepPurple);
break;
case 'drawRRect':
/// fromPoints(Offset a, Offset b)
/// 使用左上和右下角坐标来确定矩形的大小和位置
/// fromCircle({ Offset center, double radius })
/// 使用圆的圆心点坐标和半径和确定外切矩形的大小和位置
/// fromLTRB(double left, double top, double right, double bottom)
/// 使用矩形左边的X坐标、矩形顶部的Y坐标、矩形右边的X坐标、矩形底部的Y坐标来确定矩形的大小和位置
/// fromLTWH(double left, double top, double width, double height)
/// 使用矩形左边的X坐标、矩形顶部的Y坐标矩形的宽高来确定矩形的大小和位置
// 用Rect构建一个边长50,中心点坐标为50,100的矩形
Rect rect1 = Rect.fromCircle(center: Offset(80.0, 100.0), radius: 50.0);
Rect rect2 = Rect.fromCircle(center: Offset(250.0, 100.0), radius: 50.0);
// 根据上面的矩形,构建一个圆角矩形
RRect rrect1 = RRect.fromRectAndRadius(rect1, Radius.circular(0.0));
canvas.drawRRect(rrect1, painter);
RRect rrect2 = RRect.fromRectAndRadius(rect2, Radius.circular(20.0));
canvas.drawRRect(rrect2, painter);
break;
case 'drawDRRect':
//绘制两个矩形
Rect rect1 = Rect.fromCircle(center: Offset(size.width/2, 100.0), radius: 60.0);
Rect rect2 = Rect.fromCircle(center: Offset(size.width/2, 100.0), radius: 40.0);
//分别绘制外部圆角矩形和内部的圆角矩形
RRect outer = RRect.fromRectAndRadius(rect1, Radius.circular(30.0));
RRect inner = RRect.fromRectAndRadius(rect2, Radius.circular(5.0));
canvas.drawDRRect(outer, inner, painter ..color = Colors.lime);
break;
case 'drawImage':
// canvas.drawImage(image, Offset(0.0, 0.0), painter);
final src = Rect.fromLTWH(0.0, 0.0, 684.0, 442.0);
final dst = Rect.fromLTWH(0.0, 0.0, size.width, size.height);
canvas.drawImageRect(image, src, dst, painter);
break;
case 'drawStar':
var rect = Offset.zero & size;
/// 背景颜色
canvas.drawRect(rect, Paint()..color = Color(0xFF000000));
/// 绘制星形
canvas.drawPath(MathTools().regularStarPath(5, 30, Offset(50.0, 50.0)), painter..color = Colors.red);
/// 绘制多边形
canvas.save();// save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作
canvas.translate(0, 100);
canvas.scale(1.2,1.2);
canvas.drawPath(MathTools().nStarPath(4, 30, 30, Offset(40.0, 50.0)), painter);
canvas.restore();// 用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响
/// 绘制旋转星形
canvas.save();// save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作
canvas.translate(150, 50);
canvas.rotate(50 * pi / 180);
canvas.drawPath(MathTools().regularStarPath(5, 30, Offset(0,0)), painter..color = Colors.green);
canvas.restore();// 用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响
/// 绘制变形星形
canvas.save();// save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作
canvas.translate(80, 100);
canvas.skew(0.5,0.2);
canvas.drawPath(MathTools().regularStarPath(6, 30,Offset(50,50)), painter..color = Colors.lime);
canvas.restore();// 用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响
/// 绘制matrix星形
canvas.translate(250, 0);
Float64List matrix = Float64List.fromList(const <double>[
// careful, this is in _column_-major order
0.7, -0.7, 0.0, 0.0,
0.7, 0.7, 0.0, 0.0,
0.7, 0.0, 1.0, 0.0,
-70.697, 98.057, 0.0, 1.0,
]);
canvas.transform(matrix);
canvas.drawPath(MathTools().regularStarPath(5, 30,Offset(50,50)), painter..color = Colors.blue);
break;
}
//canvas.drawColor(Colors.red, BlendMode.colorDodge);
}
///控制自定义View是否需要重绘的,返回false代表这个View在构建完成后不需要重绘。
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
class MathTools {
static MathTools _mathTools;
static bool _flag;
factory MathTools(){
if (_flag == null) {
_flag = true;
}
if (_flag) {
_mathTools = new MathTools._internal();
_flag = false;
}
return _mathTools;
}
MathTools._internal();
///
///画正n角星的路径:
///
///@param num 角数
///@param R 外接圆半径
///@return 画正n角星的路径
///
Path regularStarPath(int num, double R, Offset xy) {
double degA, degB;
if (num % 2 == 1) {
//奇数和偶数角区别对待
degA = 360 / num / 2 / 2;
degB = 180 - degA - 360 / num / 2;
} else {
degA = 360 / num / 2;
degB = 180 - degA - 360 / num / 2;
}
double r = R * sin(_rad(degA)) / sin(_rad(degB));
return nStarPath(num, R, r, xy);
}
///
///画正n边形的路径
///
///@param num 边数
///@param R 外接圆半径
///@return 画正n边形的路径
///
Path regularPolygonPath(int num, double R, Offset xy) {
double r = R * cos(_rad(360 / num / 2)); //!!一点解决
return nStarPath(num, R, r, xy);
}
///
///n角星路径
///
///@param num 几角星
///@param R 外接圆半径
///@param r 内接圆半径
///@return n角星路径
///
Path nStarPath(int num, double R, double r, Offset xy) {
Path path = new Path();
double perDeg = 360 / num; //尖角的度数
double degA = perDeg / 2 / 2;
double degB = 360 / (num - 1) / 2 - degA / 2 + degA;
path.moveTo(cos(_rad(degA)) * R + xy.dx, (-sin(_rad(degA)) * R + xy.dy));
for (int i = 0; i < num; i++) {
path.lineTo(
cos(_rad(degA + perDeg * i)) * R + xy.dx, -sin(_rad(degA + perDeg * i)) * R + xy.dy);
path.lineTo(
cos(_rad(degB + perDeg * i)) * r + xy.dx, -sin(_rad(degB + perDeg * i)) * r + xy.dy);
}
path.close();
return path;
}
double _rad(double deg) {
return deg * pi / 180;
}
}