Flutter开发者

自定义View【1】

2018-10-18

上期回顾

在上一篇文章中,我们学习了自定义View的基本流程和一些相关知识,想必大家对自定义View多少都有了一定的了解。

今天我们就到代码的层次来看下如何实现和使用自定义View吧

自定义流程

在前面的文章中我们已经学习了Flutter中自定义View的简单步骤,今天我们就按照这个步骤来实现下自定义View。

CustomPainter的使用

首先,我们新建类继承于CustomPainter并且实现CustomPainter里面的paint()和shouldRepaint方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

class MyView extends CustomPainter {

@override
paint(Canvas canvas, Size size) {

}

@override
bool shouldRepaint(CustomPainter oldDelegate) {

return false;
}
}

paint方法就是Flutter中负责View绘制的地方,使用传递来的canvas和size即可完成对目标View的绘制。

shouldRepaint是控制自定义View是否需要重绘的,返回fals代表这个View在构建完成后不需要重绘。

canvas中有多个与绘制相关的方法,如drawLine()、drawRect()、drawOval()、drawOval()、等方法。

但是,仅仅使用canvas这个画布还不够,我们还需要一个画笔paint,我们使用如下代码来构建paint

1
2
3
4
5
6
7
8
9
10
 Paint _paint = new Paint()
..color = Colors.blueAccent//画笔颜色
..strokeCap = StrokeCap.round//画笔笔触类型
..isAntiAlias = true//是否启动抗锯齿
..style=PaintingStyle.fill//绘画风格,默认为填充
..blendMode=BlendMode.exclusion//颜色混合模式
..colorFilter=ColorFilter.mode(Colors.blueAccent, BlendMode.exclusion)//颜色渲染模式,一般是矩阵效果来改变的,但是flutter中只能使用颜色混合模式
..maskFilter=MaskFilter.blur(BlurStyle.inner, 3.0)//模糊遮罩效果,flutter中只有这个
..filterQuality=FilterQuality.high//颜色渲染模式的质量
..strokeWidth = 15.0;//画笔的宽度

当然,在正常的开发中一般不会使用这么多的属性,大家可以根据需要去具体的了解和使用。

举个简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

class LinePainter extends CustomPainter {
Paint _paint = new Paint()
..color = Colors.blueAccent
..strokeCap = StrokeCap.round
..isAntiAlias = true
..strokeWidth = 5.0;

@override
void paint(Canvas canvas, Size size) {
canvas.drawLine(Offset(20.0, 20.0), Offset(100.0, 100.0), _paint);
}

@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}

正如上面代码看到的,我们设置画笔的颜色为蓝色,打开抗锯齿、设置笔触的类型为圆角并且设置画笔的宽度为5.0像素。

然后在绘制的时候,绘制了一条直线,从左边(20,20)的位置到坐标为(100,100)的位置,好了这样我们便完成了最简单的view绘制。

但是,现在我们并不能去运行这个程序,我们自定义的view从根本也不是一个Widget,所以也没法直接在Widget tree中去构建,所以这个时候就要借助与CustomPaint来给我们自定义的CustomPainter来做渲染。

CustomPaint就是继承于SingleChildRenderObjectWidget的一个Widget,使用时你只需要传入你自定义的CustomPainter即可,当然CustomPaint也可以传入自己的child widget来完成Widget的组合。

下面还是来看下完整的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

import 'package:flutter/material.dart';

import 'package:flutter/material.dart';

/// ------------------------------
/// ┌─┐┬ ┬ ┬┌─┐┬ ┬
/// ├┤ │ └┬┘│ ││ │
/// └ ┴─┘┴ └─┘└─┘
/// Author : fzl flyou
/// Date : 2018/10/12 0012
/// ProjectName : test1
/// Description :
/// Version : V1.0
/// ------------------------------

void main() {
runApp(new MaterialApp(
home: PrinterLineDemo(),
));
}

class PrinterLineDemo extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return PrinterLineDemoState();
}

}

class PrinterLineDemoState extends State<PrinterLineDemo> {

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("PrinterLineDemo"),
),
body: Container(child: CustomPaint(painter: LinePainter(),),),
);
}
}

class LinePainter extends CustomPainter {
Paint _paint = new Paint()
..color = Colors.blueAccent
..strokeCap = StrokeCap.round
..isAntiAlias = true
..strokeWidth = 5.0;

@override
void paint(Canvas canvas, Size size) {
canvas.drawLine(Offset(20.0, 20.0), Offset(100.0, 100.0), _paint);
}

@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}

看下效果:

当然,我们可以随意改变画笔的颜色或者多绘制几条直线?

绘制相关

刚才我们看了下绘制直线,接下来看一下其他绘制相关的方法。

绘制点drawPoints

drawPoints(PointMode pointMode, List points, Paint paint)

绘制点也是非常的简单,只需要传入PointMode枚举,坐标list和paint即可

PointMode的枚举类型有三个,points(点),lines(线,隔点连接),polygon(线,相邻连接)

看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

@override
void paint(Canvas canvas, Size size) {
canvas.drawPoints(
PointMode.points,
[
Offset(20, 20),
Offset(100.0, 100.0),
Offset(100.0, 200.0),
Offset(200.0, 200.0),
Offset(200.0, 100.0),
Offset(280.0, 20.0),
Offset(20, 20),
],
_paint);
}

为了方便演示,我们在上面定义了7个点,第一个和最后一个点重合。

然后我们设置PointMode为points看下效果。

然后我们把PointMode改为lines

跟大家想象中的一样吗?

PointMode为lines时,两个点相互连接,也就是说第一个和第二个点连接,第三个跟第四个连接,如果最后只有一个点就舍弃不连接了,在我们的例子中有7个点,所以图中只有三条连线。

然后我们把PointMode改为lines

对,你看的没有错跟上面绘制线段的效果是一样的,相邻点互相连接。

绘制圆rawCircle

canvas.drawCircle(offset, radius, paint)

绘制圆也很简单,仅仅圆心的坐标、半径和paint即可。

来看下用法:

1
2
3
4
5
6
7
8


@override
void paint(Canvas canvas, Size size) {

canvas.drawCircle(Offset(100, 100), 100, _paint);

}

可以看到我们在坐标(100,100)的位置绘制了一个半径为红色的圆。

但是,我们可以看到这个圆都被红色填充了,明明我们在前面定义画笔的宽度为5来着,怎么回填充满呢?

其实是因为,paint的默认绘画风格为填充,我们尝试把style改为PaintingStyle.stroke。

再来看下效果:

绘制椭圆drawOval

drawOval(Rect rect, Paint paint)

绘制椭圆就相对简单很多,只需要传入Rect和paint即可,在前面我们已经讲过了使用Rect便可确认这个矩形的大小和位置。

其实,Rect也有多种构建方式:

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坐标矩形的宽高来确定矩形的大小和位置

所以,这4种方式无论你使用那种都是一样的,都可以确定这个矩形的位置和大小,淡然这个椭圆也是在这个矩形之中内切的。

Rect rect1= Rect.fromPoints(Offset(50.0, 50.0), Offset(130.0, 100.0));
canvas.drawOval(rect1, _paint);

宽度大于高度的椭圆

Rect rect2= Rect.fromPoints(Offset(50.0, 150.0), Offset(130.0, 300.0));
canvas.drawOval(rect2, _paint);

高度大于宽度的椭圆

Rect rect3= Rect.fromPoints(Offset(50.0, 320.0), Offset(130.0, 400.0));
canvas.drawOval(rect3, _paint);

高度等于宽度的,对不起这是圆

绘制圆弧drawArc

drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint)

绘制圆弧也很简单,首先还是需要Rect来确认圆弧的位置,还需要开始的弧度、结束的弧度、是否使用中心点绘制、以及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π |

看下例子:

1
2
3
4
5
6

double PI = 3.1415926;
var sweepAngle = PI / 2;
var startAngle = 0;
Rect rect = Rect.fromCircle(center: Offset(100.0, 100), radius: 80);
canvas.drawArc(rect, startAngle, sweepAngle, false, _paint);

我们定义π为3.1415926,定义开始的角度为0°扫过的角度为PI / 2(90°),设置userCenter为false

看下效果:

看起来好像真的是90°啊,还是打开userCenter看下效果。

嗯,这样看来确实是一个90°的圆弧啊。

绘制圆角矩形drawDRRect

drawRRect(RRect rrect, Paint paint)

其实使用起来也是非常的简单,使用RRect确定矩形大小及弧度,使用paint来完成绘制。

RRect构建起来也非常的方便,直接使用fromRectAndRadius即可

RRect.fromRectAndRadius(rect, radius)

rect依然用来表示矩形的位置和大小,radius用来表示圆角的大小。

1
2
3
4
5


Rect rect = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 60.0);
RRect rRect = RRect.fromRectAndRadius(rect, Radius.circular(5.0));
canvas.drawRRect(rRect, _paint);

设置圆角度数为5

1
2
3
4
5


Rect rect = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 60.0);
RRect rRect = RRect.fromRectAndRadius(rect, Radius.circular(60.0));
canvas.drawRRect(rRect, _paint);

设置圆角度数等于Rect radius大小。

绘制双圆角矩形drawRRect

drawDRRect(RRect outer, RRect inner, Paint paint)

和drawRRect类似,使用RRect确定内部、外部矩形大小及弧度,使用paint来完成绘制。

1
2
3
4
5
6
7
8
9
10
11

@override
void paint(Canvas canvas, Size size) {

Rect rect4 = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 60.0);
Rect rect5 = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 40.0);

RRect rRectOut = RRect.fromRectAndRadius(rect4, Radius.circular(10.0));
RRect rRectInner = RRect.fromRectAndRadius(rect5, Radius.circular(10.0));
canvas.drawDRRect(rRectOut, rRectInner, _paint);
}

我们使用Rect.fromCircle来创建Rect,使用RRect.fromRectAndRadius来创建RRect

可以看到两个圆角矩形哦,淡然我们可以尝试调整角度的度数大小。

1
2
3
4
5
6
7
8
9
10
11

@override
void paint(Canvas canvas, Size size) {

Rect rect4 = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 60.0);
Rect rect5 = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 40.0);

RRect rRectOut = RRect.fromRectAndRadius(rect4, Radius.circular(40.0));
RRect rRectInner = RRect.fromRectAndRadius(rect5, Radius.circular(30.0));
canvas.drawDRRect(rRectOut, rRectInner, _paint);
}

1
2
3
4
5
6
7
8
9
10
11

@override
void paint(Canvas canvas, Size size) {

Rect rect4 = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 60.0);
Rect rect5 = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 40.0);

RRect rRectOut = RRect.fromRectAndRadius(rect4, Radius.circular(60.0));
RRect rRectInner = RRect.fromRectAndRadius(rect5, Radius.circular(40.0));
canvas.drawDRRect(rRectOut, rRectInner, _paint);
}

当然,你可以可以调整两个圆弧的位置来获得交叉的圆弧效果。

好了,今天先说这么多,还有一些其他的绘制方法下篇文章再来看,哈

小结

  • 熟悉绘制的基本流程
  • 掌握绘制常用的对象和方法
  • 掌握自定View的使用

试一试

把今天讲到的绘制方法都尝试下,包括paint的属性等。

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

打赏请备注姓名或者昵称,方便我后期统计哦

关注公众号,及时查阅最新文章