一,概述
我们知道,应用开发如果单纯只靠系统提供的控件,对于那些较为绚烂界面效果来说是远远不够的,这就需要开发者自己去自定义绘制Widget。
当然,自定义Widget讲究灵活性,同一种效果可以由多种实现方案,我们要做的就是找到代价最小、最高效的解决方案。
二,Flutter自定义绘制Widget
从如何使用Canvas draw/paint我们了解到,在Flutter中使用自绘方式自定义Widget,大致需要以下步骤:
-
-
继承CustomPainter并重写paint方法和shouldRepaint方法
-
在写paint方法中绘制内容
-
使用CustomPaint来构建Widget
- 示例:
举个栗子,比如下面代码就实现了30*30的网格
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {// This widget is the root of your application.@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: BackGrad(),);}}class BackGrid extends StatelessWidget { @override Widget build(BuildContext context) { return Container( child: Center( // 使用CustomPaint child: CustomPaint( size: Size(300, 300), painter: MyPainter(), ), ), ); } } class MyPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { double eWidth = size.width / 30; double eHeight = size.height / 30; //画棋盘背景 var paint = Paint() ..isAntiAlias = true ..style = PaintingStyle.fill //填充 ..color = Color(0x77cdb175); //背景为纸黄色 canvas.drawRect(Offset.zero & size, paint); //画棋盘网格 paint ..style = PaintingStyle.stroke //线 ..color = Color(0xFF888888) ..strokeWidth = 1.0; for (int i = 0; i <= 30; ++i) { double dy = eHeight * i; canvas.drawLine(Offset(0, dy), Offset(size.width, dy), paint); } for (int i = 0; i <= 30; ++i) { double dx = eWidth * i; canvas.drawLine(Offset(dx, 0), Offset(dx, size.height), paint); } } @override bool shouldRepaint(CustomPainter oldDelegate) => false; }
-
显示效果:
-
Flutter绘制相关知识
和Android开发中的自定义View类似,Flutter中的绘制也是依靠Canvas和Paint来实现的1.1 Canvas
画布,为开发者提供了点、线、矩形、圆形、嵌套矩形等绘制方法。
1.2 Paint
画笔,可以设置抗锯齿,画笔颜色,粗细,填充模式等属性,绘制时可以定义多个画笔以满足不同的绘制需求。
1.3 Offset
坐标,可以用来表示某个点在画布中的坐标位置。
1.4 Rect
矩形,在图形的绘制中,一般都是分区域绘制的,这个区域一般都是一个矩形,在绘制中通常使用Rect来存储绘制的位置信息。
1.5 坐标系
在Flutter中,坐标系原点(0,0)位于左上角,X轴向右变大,Y轴向下变大。
下面我们看一下Paint的常用基本属性设置
Paint _paint = new Paint() // 画笔颜色 ..color = Colors.red // 画笔笔触类型 // round-画笔笔触呈半圆形轮廓开始和结束 // butt-笔触开始和结束边缘平坦,没有外延 // square-笔触开始和结束边缘平坦,向外延伸长度为画笔宽度的一半 ..strokeCap = StrokeCap.round //是否启动抗锯齿 ..isAntiAlias = true //绘画风格,默认为填充,有fill和stroke两种 ..style=PaintingStyle.fill ..blendMode=BlendMode.exclusion//颜色混合模式 ..colorFilter=ColorFilter.mode(Colors.blueAccent, BlendMode.exclusion)//颜色渲染模式 ..maskFilter=MaskFilter.blur(BlurStyle.inner, 3.0)//模糊遮罩效果 ..filterQuality=FilterQuality.high//颜色渲染模式的质量 ..strokeWidth = 15.0;//画笔的宽度复制代码
-
2、Flutter绘制方法介绍
接下来我们了解一下Flutter中Canvas的各种绘制方法
PS:以下各种绘制方法所使用的_paint定义如下:
var _paint = Paint() ..color = Color(0xFFFf0000) ..strokeWidth = 4 ..style = PaintingStyle.stroke ..isAntiAlias = true;
2.1 绘制点
void main() => runApp(MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: BackGrad(), ); } } class BackGrad extends StatelessWidget { @override Widget build(BuildContext context) { return new Container( child: Center( child: CustomPaint(//使用CustomPaint size: Size(300, 300), painter: MyPainter(), ), ), ); } } class MyPainter extends CustomPainter { @override ///[画背景] void paint(Canvas canvas, Size size) { double eWidth = size.width/30; double eHeight = size.height/30; //画棋盘背景 var paint = Paint() ..isAntiAlias = true ..style = PaintingStyle.fill//填充 ..color = Color(0x77cdb175);//背景为纸黄色 canvas.drawRect(Offset.zero & size, paint); //画棋盘网格 paint ..style = PaintingStyle.fill //线 ..color = Color(0xFF888888) ..strokeWidth = 1.0; for(int i = 0; i<=30;++i){ double dy = eHeight * i; canvas.drawLine(Offset(0,dy), Offset(size.width,dy), paint); } for(int i = 0; i <= 30; ++i){ double dx = eWidth * i; canvas.drawLine(Offset(dx, 0), Offset(dx, size.height), paint); } ///[绘制点] var _paint = Paint() ..color = Color(0xffff0000) //画笔颜色 ..strokeWidth = 4 //画笔粗细 ..style = PaintingStyle.stroke ..isAntiAlias = true; List<Offset> points = [ Offset(0, 0), Offset(30, 50), Offset(20, 80), Offset(100, 40), Offset(150, 90), Offset(60, 110), Offset(260, 160), ]; canvas.drawPoints(prefix0.PointMode.points,points,_paint); } @override bool shouldRepaint(CustomPainter oldDelegate) => false; }
PointMode是一个枚举类:
enum PointMode { points,// 绘制点 lines,// 绘制点,且数组内隔点相连,如1-2相连,3-4相连。如最后只剩下一个点,则不去绘制该点 polygon,// 数组内相邻点连接 }
结果:
-
2.2 绘制线
void main() => runApp(MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: BackGrad(), ); } } class BackGrad extends StatelessWidget { @override Widget build(BuildContext context) { return new Container( child: Center( child: CustomPaint(//使用CustomPaint size: Size(300, 300), painter: MyPainter(), ), ), ); } } class MyPainter extends CustomPainter { @override ///[画背景] void paint(Canvas canvas, Size size) { double eWidth = size.width/30; double eHeight = size.height/30; //画棋盘背景 var paint = Paint() ..isAntiAlias = true ..style = PaintingStyle.fill//填充 ..color = Color(0x77cdb175);//背景为纸黄色 canvas.drawRect(Offset.zero & size, paint); //画棋盘网格 paint ..style = PaintingStyle.fill //线 ..color = Color(0xFF888888) ..strokeWidth = 1.0; for(int i = 0; i<=30;++i){ double dy = eHeight * i; canvas.drawLine(Offset(0,dy), Offset(size.width,dy), paint); } for(int i = 0; i <= 30; ++i){ double dx = eWidth * i; canvas.drawLine(Offset(dx, 0), Offset(dx, size.height), paint); } ///[绘制线] var _paint = Paint() ..color = Color(0xffff0000) //画笔颜色 ..strokeWidth = 4 //画笔粗细 ..style = PaintingStyle.stroke ..isAntiAlias = true; var _startPoint = Offset(30, 30);//起始点 var _endPoint = Offset(100,170);//结束点 canvas.drawLine(_startPoint, _endPoint, _paint); } @override bool shouldRepaint(CustomPainter oldDelegate) => false; }
结果:
-
2.3 绘制矩形
首先我们了解一下矩形Rect的构建:// 利用矩形左边的X坐标、矩形顶部的Y坐标、矩形右边的X坐标、矩形底部的Y坐标确定矩形的大小和位置 Rect.fromLTRB(double left, double top, double right, double bottom) // 利用矩形的左边的X轴、顶边的Y轴位置配合设置矩形的长和宽来确定矩形的大小和位置 Rect.fromLTWH(double left, double top, double width, double height) // 利用矩形的中心点和矩形所在圆的半径来确定矩形的大小和位置,此方法确定的是一个正方形 Rect.fromCircle({ Offset center, double radius }) // 利用矩形的左上角和右下角的坐标确定矩形的大小和位置, // 需要注意的是两点坐标中,如果X轴相同或者Y轴相同,确定的是一条线, // 如果两点XY坐标都是同一个数字,确定的是一个点。 Rect.fromPoints(Offset a, Offset b)
绘制矩形的方法
void main() => runApp(MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: BackGrad(), ); } } class BackGrad extends StatelessWidget { @override Widget build(BuildContext context) { return new Container( child: Center( child: CustomPaint(//使用CustomPaint size: Size(300, 300), painter: MyPainter(), ), ), ); } } class MyPainter extends CustomPainter { @override ///[画背景] void paint(Canvas canvas, Size size) { double eWidth = size.width/30; double eHeight = size.height/30; //画棋盘背景 var paint = Paint() ..isAntiAlias = true ..style = PaintingStyle.fill//填充 ..color = Color(0x77cdb175);//背景为纸黄色 canvas.drawRect(Offset.zero & size, paint); //画棋盘网格 paint ..style = PaintingStyle.fill //线 ..color = Color(0xFF888888) ..strokeWidth = 1.0; for(int i = 0; i<=30;++i){ double dy = eHeight * i; canvas.drawLine(Offset(0,dy), Offset(size.width,dy), paint); } for(int i = 0; i <= 30; ++i){ double dx = eWidth * i; canvas.drawLine(Offset(dx, 0), Offset(dx, size.height), paint); } ///[绘制矩形] var _paint = Paint() ..color = Color(0xffff0000) //画笔颜色 ..strokeWidth = 4 //画笔粗细 ..style = PaintingStyle.stroke ..isAntiAlias = true; //1.Rect.fromLTRB(20, 60, 100, 180) canvas.drawRect(Rect.fromLTRB(20, 60, 100, 180), _paint); //2.Rect.fromLTWH(40, 100, 90, 80) canvas.drawRect(Rect.fromLTWH(40, 100, 90, 80), _paint); //3.Rect.fromCircle(center:Offset(100, 180),radius: 50) canvas.drawRect(Rect.fromCircle(center:Offset(100, 180),radius: 50), _paint); //4.Rect.fromPoints(Offset(100, 180), Offset(200, 100)) canvas.drawRect(Rect.fromPoints(Offset(100, 180), Offset(200, 100)), _paint); } @override bool shouldRepaint(CustomPainter oldDelegate) => false; }
结果:
-
2.4 绘制圆角矩形
圆角矩形的构建方式有以下几种:// 利用矩形的左边的X坐标、矩形顶部的Y坐标、矩形右边的X坐标、矩形底部的Y坐标 // 以及可选的四个顶点圆角来确定圆角矩形 RRect.fromLTRBAndCorners( double left, double top, double right, double bottom, { Radius topLeft: Radius.zero, Radius topRight: Radius.zero, Radius bottomRight: Radius.zero, Radius bottomLeft: Radius.zero, }) // 需要先定义一个Rect,使用方式和fromLTRBAndCorners类似 RRect.fromRectAndCorners( Rect rect, { Radius topLeft: Radius.zero, Radius topRight: Radius.zero, Radius bottomRight: Radius.zero, Radius bottomLeft: Radius.zero } ) // 顶点X轴Y轴的圆角设置相同 const Radius.circular(double radius) // 顶点X轴Y轴的圆角设置可以不相同 const Radius.elliptical(this.x, this.y)
绘制圆角矩形
-
void drawRRect(RRect rrect, Paint paint)
-
-
void main() => runApp(MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: BackGrad(), ); } } class BackGrad extends StatelessWidget { @override Widget build(BuildContext context) { return new Container( child: Center( child: CustomPaint(//使用CustomPaint size: Size(300, 300), painter: MyPainter(), ), ), ); } } class MyPainter extends CustomPainter { @override ///[画背景] void paint(Canvas canvas, Size size) { double eWidth = size.width/30; double eHeight = size.height/30; //画棋盘背景 var paint = Paint() ..isAntiAlias = true ..style = PaintingStyle.fill//填充 ..color = Color(0x77cdb175);//背景为纸黄色 canvas.drawRect(Offset.zero & size, paint); //画棋盘网格 paint ..style = PaintingStyle.fill //线 ..color = Color(0xFF888888) ..strokeWidth = 1.0; for(int i = 0; i<=30;++i){ double dy = eHeight * i; canvas.drawLine(Offset(0,dy), Offset(size.width,dy), paint); } for(int i = 0; i <= 30; ++i){ double dx = eWidth * i; canvas.drawLine(Offset(dx, 0), Offset(dx, size.height), paint); } ///[绘制圆角矩形] var _paint = Paint() ..color = Color(0xffff0000) //画笔颜色 ..strokeWidth = 4 //画笔粗细 ..style = PaintingStyle.stroke ..isAntiAlias = true; //1.RRect.fromLTRBAndCorners(20,60,110,180,topLeft:Radius.circular(30)) canvas.drawRRect(RRect.fromLTRBAndCorners(20,60,110,180,topLeft:Radius.circular(30)), _paint); //2.RRect.fromLTRBAndCorners(20,60,110,180,topLeft:Radius.elliptical(30,100)) canvas.drawRRect(RRect.fromLTRBAndCorners(20,60,110,180,topLeft:Radius.elliptical(30,100)), _paint); } @override bool shouldRepaint(CustomPainter oldDelegate) => false; }
结果:
// 利用矩形的左边的X坐标、矩形顶部的Y坐标、矩形右边的X坐标、矩形底部的Y坐标 // 以及一个Radius来确定圆角矩形 RRect.fromLTRBR(double left, double top, double right, double bottom, Radius radius) // 需要先定义一个Rect,使用方式和fromLTRBR类似 RRect.fromRectAndRadius(Rect rect, Radius radius)
// 利用矩形的左边的X坐标、矩形顶部的Y坐标、矩形右边的X坐标、矩形底部的Y坐标 // 以及设置四个顶点的X轴相同圆角,设置四个顶点的Y轴相同圆角来确定圆角矩形 RRect.fromLTRBXY(double left, double top, double right, double bottom, double radiusX, double radiusY) // 需要先定义一个Rect,使用方式和fromLTRBXY类似 RRect.fromRectXY(Rect rect, double radiusX, double radiusY)
圆角矩形绘制有一个需要注意的地方,如果设置矩形的半径和圆角的半径相等,则绘制出来的是一个圆// 设置矩形的半径和圆角的半径不相等,效果如下面左图 Rect rect = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 50.0); RRect rRect = RRect.fromRectAndRadius(rect, Radius.circular(10.0)); // 设置矩形的半径和圆角的半径相等,效果如下面右图 Rect rect = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 50.0); RRect rRect = RRect.fromRectAndRadius(rect, Radius.circular(50.0));
-
2.5 绘制双圆角矩形
Flutter中提供了绘制双圆角矩形的方法
void drawDRRect(RRect outer, RRect inner, Paint paint)
这里需要注意的是,内圆角矩形的半径不能大于外圆角矩形的半径,否则无法绘制。
// 第一种:外圆角矩形的半径大于内圆角矩形半径,如下面左图 Rect rectOut = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 80.0); Rect rectInner = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 40.0); // 第二种:外圆角矩形的半径等于内圆角矩形半径,如下面中图 Rect rectOut = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 80.0); Rect rectInner = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 80.0); // 第三种:外圆角矩形的半径小于内圆角矩形半径,无法绘制,如下面右图 Rect rectOut = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 80.0); Rect rectInner = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 81.0);
接下来绘制上面三种情况下的双圆角矩形
RRect rRectOut = RRect.fromRectAndRadius(rectOut, Radius.circular(10.0)); RRect rRectInner = RRect.fromRectAndRadius(rectInner, Radius.circular(30.0)); canvas.drawDRRect(rRectOut, rRectInner, _paint);
-
2.6 绘制圆
// 根据圆心坐标和圆的半径确定圆的位置和大小void drawCircle(Offset c, double radius, Paint paint)
画一个圆
// 绘制圆,Paint默认填充PaintingStyle.fill,如下面左图canvas.drawCircle(Offset(100.0, 100.0), 50.0, _paint); // 绘制圆,Paint设置为不填充,如下面右图_paint.style = PaintingStyle.stroke;canvas.drawCircle(Offset(100.0, 100.0), 50.0, _paint);
-
2.7 绘制椭圆
// Rect矩形区域的确定参见前文 void drawOval(Rect rect, Paint paint) // 椭圆的宽度大于高度,如下面左图 Rect rect= Rect.fromPoints(Offset(50.0, 50.0), Offset(130.0, 100.0)); // 椭圆的宽度小于高度,如下面中图 Rect rect= Rect.fromPoints(Offset(40.0, 80.0), Offset(80.0, 170.0)); // 椭圆的宽度等于高度,绘制出的是圆,如下面右图 Rect rect= Rect.fromPoints(Offset(80.0, 70.0), Offset(180.0, 170.0));
绘制一个椭圆
canvas.drawOval(rect, _paint);
-
2.8 绘制圆弧
// rect:矩形区域// startAngle:开始的弧度 // sweepAngle:扫过的弧度,正数顺时针,负数逆时针 // useCenter:是否使用中心点绘制void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, 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π 使用π需要引入:
import 'dart:math';
下面我们尝试绘制一些圆弧
var rect = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 50.0); // 开始度数为90度,顺时针扫度数90度,如下方左图 canvas.drawArc(rect, -pi / 2, pi / 2, false, _paint); // 开始度数为0度,顺时针扫度数60度,如下方中图 canvas.drawArc(rect, 0, pi / 3, false, _paint); // 开始度数为0度,逆时针扫度数180度,使用中心点绘制,如下方右图 canvas.drawArc(rect, 0, -pi, true, _paint);复制代码
-
2.9 绘制路径
void drawPath(Path path, Paint paint)
Path方法介绍
// 将路径的起始点移动到指定的坐标点上 void moveTo(double x, double y) // 将起始点和终点连成线段,如果没有显式的指明起始点坐标,默认为(0,0) void lineTo(double x, double y) // 起始点(30,30),终点(100,100),如下面左图 Path path = new Path(); path.moveTo(30, 30); path.lineTo(100, 100); canvas.drawPath(path, _paint); // 起始点默认(0,0),终点(100,100),如下面右图 Path path = new Path(); path.lineTo(100, 100); canvas.drawPath(path, _paint);复制代码
// 相对于当前位置坐标在X轴上偏移dx,在Y轴上偏移dy void relativeMoveTo(double dx, double dy) // 起始点(0,0),和(20,20)之间连线,后移动到(30,30),和(70,70)之间连线 Path path = new Path(); path.lineTo(20, 20); path.moveTo(30, 30); path.lineTo(70, 70); canvas.drawPath(path, _paint); // 起始点(0,0),和(20,20)之间连线, // 后以当前(20,20)坐标为起始点X轴偏移30,Y轴偏移30, // 即偏移后的新坐标(50,50)和(70,70)之间连线 Path path = new Path(); path.lineTo(20, 20); path.relativeMoveTo(30, 30); path.lineTo(70, 70); canvas.drawPath(path, _paint);
// 确定相对于当前位置坐标,在X轴上偏移dx,在Y轴上偏移dy后的新坐标, // 确定好之后当前位置坐标和新坐标之间连线 void relativeLineTo(double dx, double dy) // (0,0)和(10,10)连线,(10,10)和(20,20)连线,如下面左图 Path path = new Path(); path.lineTo(10, 10); path.lineTo(20, 20); canvas.drawPath(path, _paint); // (0,0)和(10,10)连线,(10,10)和当前位置偏移后的新坐标(30,30)连线 Path path = new Path(); path.lineTo(10, 10); path.relativeLineTo(20, 20); canvas.drawPath(path, _paint);
// 绘制二阶贝塞尔曲线 // 绘制需要一个起始点、一个终点和一个控制点 // 此方法的前两个参数是控制点的坐标,后两个参数是终点的坐标 void quadraticBezierTo(double x1, double y1, double x2, double y2) // 绘制二阶贝塞尔曲线 // 绘制需要一个起始点、一个终点和一个控制点 // 此方法的前两个参数是控制点的坐标,后两个参数是终点的坐标 // 和quadraticBezierTo不同在于终点坐标的位置是当前起点位置坐标X轴偏移x2,Y轴偏移y2后的坐标 // 举例(略) void relativeQuadraticBezierTo(double x1, double y1, double x2, double y2) // 绘制曲线,如下面左图 Path path = new Path(); path.moveTo(150, 100); path.quadraticBezierTo(250, 40, 250, 200); canvas.drawPath(path, _paint); // 绘制控制点 _paint.strokeWidth = 8; _paint.color = Colors.blue; canvas.drawPoints(PointMode.points, [Offset(250, 40)], _paint); // 绘制曲线,如下面中图 Path path = new Path(); path.moveTo(150, 100); path.quadraticBezierTo(200, 200, 250, 200); canvas.drawPath(path, _paint); // 绘制控制点 _paint.strokeWidth = 8; _paint.color = Colors.blue; canvas.drawPoints(PointMode.points, [Offset(200, 200)], _paint); // 绘制曲线,如下面右图 Path path = new Path(); path.moveTo(150, 100); path.quadraticBezierTo(60, 130, 150, 200); canvas.drawPath(path, _paint); // 绘制控制点 _paint.strokeWidth = 8; _paint.color = Colors.blue; canvas.drawPoints(PointMode.points, [Offset(60, 130)], _paint);
// 绘制三阶贝塞尔曲线 // 绘制需要一个起始点、一个终点和两个控制点 // 此方法的前四个参数分别是两个控制点的XY坐标,后两个参数是终点的坐标 void cubicTo(double x1, double y1, double x2, double y2, double x3, double y3) // 绘制三阶贝塞尔曲线 // 绘制需要一个起始点、一个终点和两个控制点 // 此方法的前四个参数分别是两个控制点的XY坐标,后两个参数是终点的坐标 // 和cubicTo不同在于终点坐标的位置是当前起点位置坐标X轴偏移x2,Y轴偏移y2后的坐标 // 举例(略) void relativeCubicTo(double x1, double y1, double x2, double y2, double x3, double y3)
下面使用cubicTo绘制心形效果
var width = 300; var height = 300; Path leftPath = new Path(); // 画笔移动到左边曲线起点 leftPath.moveTo(width / 2, height / 4 + 20); leftPath.cubicTo( width / 8, height / 10, width / 13, (height * 2) / 5, width / 2, height - height / 4); // 绘制心形左边曲线 canvas.drawPath(leftPath, _paint); Path rightPath = new Path(); // 画笔移动到右边曲线起点 rightPath.moveTo(width / 2, height / 4 + 20); rightPath.cubicTo( width - width / 8, height / 10, (width * 12) / 13, (height * 2) / 5, width / 2, height - height / 4); // 绘制心形右边曲线 canvas.drawPath(rightPath, _paint); // 绘制两个控制点 _paint.strokeWidth = 8; _paint.color = Colors.blue; canvas.drawPoints( PointMode.points, [ Offset(width / 8, height / 10), Offset(width / 13, (height * 2) / 5), Offset(width - width / 8, height / 10), Offset((width * 12) / 13, (height * 2) / 5) ], _paint);
// 画弧线 // rect:矩形区域 // startAngle:开始的弧度 // sweepAngle:扫过的弧度,正数顺时针,负数逆时针 // forceMoveTo:true-画笔从当前位置抬起来,移动到弧线起点 // false-画笔从当前位置不抬起来直接连线到弧线起点 void arcTo(Rect rect, double startAngle, double sweepAngle, bool forceMoveTo) // 效果如下面左图,画笔从(80, 100)抬起来,移动到弧线起点 boo forceMoveTo = true; // 效果如下面右图,画笔从(80, 100)不抬起来直接连线到弧线起点 boo forceMoveTo = false; Path path = new Path(); path.moveTo(20, 40); path.lineTo(80, 100); path.arcTo(new Rect.fromLTWH(60, 60, 100, 100), 0, pi / 2, forceMoveTo); canvas.drawPath(path, _paint);
// 封闭当前路径,以上面代码为例,如下图 void close();
其他方法
// 为path添加矩形 void addRect(Rect rect); // 为path添加圆角矩形 void addRRect(RRect rrect); // 为path添加椭圆 void addOval(Rect oval) // 为path添加圆弧 void addArc(Rect oval, double startAngle, double sweepAngle); // 为path添加多边形,points-坐标数组,close-是否首位封闭 void addPolygon(List<Offset> points, bool close); // 为path添加一个path,偏移offset坐标 void addPath(Path path, Offset offset, {Float64List matrix4}) // path路径是否包含point坐标点 bool contains(Offset point) // 重置路径 void reset(); // 给路径做matrix4变换 Path transform(Float64List matrix4)
下面举一个addPath的例子
Path path = new Path(); path.lineTo(20, 20); Path path1 = new Path(); path1.lineTo(40, 40); // 如下图,addPath方法就是在(30,30)坐标位置偏移(40,40)并连线 path.addPath(path1, Offset(30, 30)); canvas.drawPath(path, _paint);
三,总结
本文列举了Flutter开发中,Canvas绘制流程常用的方法并提供了简单的示例,可以看出,和Android的Canvas还是很相似的,上手也非常的快。要做出酷炫的Widget,最好还是需要配合动画效果,当然,用canvas做些简单的icon也是可以的。