Flutter自定义绘制Widget初探,作为Android开发程序员

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;
}

显示效果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image.png

1、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 绘制点

List points = [
Offset(0, 0),
Offset(30, 50),
Offset(20, 80),
Offset(100, 40),
Offset(150, 90),
Offset(60, 110),
Offset(260, 160),
];
canvas.drawPoints(PointMode.points, points, _paint);

PointMode是一个枚举类:

enum PointMode {
points,// 绘制点
lines,// 绘制点,且数组内隔点相连,如1-2相连,3-4相连。如最后只剩下一个点,则不去绘制该点
polygon,// 数组内相邻点连接
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image-2.png

2.2 绘制线

var _startPoint = Offset(30, 30);// 起始点
var _endPoint = Offset(100, 170);// 终点
canvas.drawLine(_startPoint, _endPoint, _paint);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image-3.png

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 drawRect(Rect rect, Paint paint)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image-4.png

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)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image-5.png

// 利用矩形的左边的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)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image-6.png

// 利用矩形的左边的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)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image-7.png

圆角矩形绘制有一个需要注意的地方,如果设置矩形的半径和圆角的半径相等,则绘制出来的是一个圆

// 设置矩形的半径和圆角的半径不相等,效果如下面左图
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));

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image-8.png

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);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image-9.png

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);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image-10.png

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);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image-11.png

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
30°π/6
45°π/4
60°π/3
90°π/2
120°2π/3
180°π
270°3π/2
360°

使用π需要引入:

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);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image-12.png

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);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image-13.png

// 相对于当前位置坐标在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);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image-14.png

// 确定相对于当前位置坐标,在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);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image-15.png

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

写在最后

在技术领域内,没有任何一门课程可以让你学完后一劳永逸,再好的课程也只能是“师傅领进门,修行靠个人”。“学无止境”这句话,在任何技术领域,都不只是良好的习惯,更是程序员和工程师们不被时代淘汰、获得更好机会和发展的必要前提。

如果你觉得自己学习效率低,缺乏正确的指导,可以一起学习交流!

加入我们吧!群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

.jpg" />

写在最后

在技术领域内,没有任何一门课程可以让你学完后一劳永逸,再好的课程也只能是“师傅领进门,修行靠个人”。“学无止境”这句话,在任何技术领域,都不只是良好的习惯,更是程序员和工程师们不被时代淘汰、获得更好机会和发展的必要前提。

如果你觉得自己学习效率低,缺乏正确的指导,可以一起学习交流!

加入我们吧!群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flutter是一种跨平台的移动应用开发框架,它使用Dart语言进行编写。在Flutter中,可以通过自定义控件来实现特定的功能或者界面效果。下面是Flutter定义控件开发的指南: 1. 创建一个新的控件类:在Flutter中,可以通过继承StatefulWidget或者StatelessWidget来创建一个新的控件类。StatefulWidget是有状态的控件,可以根据需要更新状态并重新渲染界面;StatelessWidget是无状态的控件,一旦创建就不会再改变。 2. 实现build方法:在控件类中,需要实现一个build方法,该方法返回一个Widget对象,用于描述控件的外观和行为。可以使用Flutter提供的丰富的Widget库来构建界面,也可以自定义绘制逻辑。 3. 添加属性:通过在控件类中定义属性,可以让用户在使用该控件时传入不同的参数,从而实现控件的可配置性。可以使用构造函数来接收属性,并在build方法中使用这些属性来构建界面。 4. 处理用户交互:如果需要处理用户的点击、滑动等交互操作,可以在控件类中添加相应的回调函数,并将其传递给子控件。子控件可以通过调用回调函数来通知父控件发生了交互事件。 5. 更新状态:如果创建的是有状态的控件,可以通过调用setState方法来更新控件的状态,并触发界面的重新渲染。在setState方法中,可以修改控件的属性值,然后Flutter会自动调用build方法来重新构建界面。 6. 使用自定义控件:在其他地方使用自定义控件时,只需要创建该控件的实例,并将其添加到界面中即可。可以通过设置属性来配置控件的外观和行为。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值