Flutter 自定义画笔案例

news/2025/1/20 6:00:12/文章来源:https://www.cnblogs.com/inthecloud/p/18336611

首先让我们来看下这张图

当UI做的设计图中有这么一个元素,我想大多数人第一反应就是叫UI切图,然后直接使用Image加载,我一开始也是这么做的,毕竟省时省力省心。

但是由于后面需要针对不同的状态设置不同的颜色,我不想写过多判断语句来切换图标(我目前的做法是实现一个枚举类,然后拓展该枚举,针对每个状态设置不同的颜色,然后直接通过枚举拿到对应状态的颜色传入)

图片分析

从图片上,我们可以看到主要由以下部分构成:

  1. 外层阴影:给图标提供立体效果。
  2. 绿色底层圆:用来描绘背景。
  3. 深绿色顶层圆:叠加在底层圆上,进一步增加层次感。
  4. 白色闪电图标:位于圆的中央,表示充电。

为什么使用画笔而不是直接使用图片

使用画笔绘制图形而非直接使用图片的好处包括:

  1. 可扩展性:矢量图形可以根据不同屏幕尺寸动态调整,而不会失真。
  2. 自定义性:使用画笔可以随意调整颜色、形状等属性,更加灵活。
  3. 性能优化:绘制图形往往比加载位图更高效,特别是在需要频繁重绘的场景中。

实现步骤

下面我们逐步实现这个效果,希望能让各位有所收获

1. 创建一个 ChargePainter

首先,我们需要创建一个 CustomPainter 的子类 ChargePainter。在这个类中,我们将定义颜色属性,并在 paint 方法中实现绘制逻辑。

class ChargePainter extends CustomPainter {// 底层颜色final Color bottomColor;// 顶层颜色final Color topColor;ChargePainter({this.bottomColor = const Color(0xFF1ACC2C),this.topColor = const Color(0xFF1FA22C),});@overridevoid paint(Canvas canvas, Size size) {Paint paint = Paint();double circleSize = size.width / 2;// 代码的详细解释将放在后续步骤中}@overridebool shouldRepaint(covariant ChargePainter oldDelegate) {return oldDelegate.bottomColor != bottomColor || oldDelegate.topColor != topColor;}
}

解释一下上面的代码,我们继承了一个CustomPainter,它给提供了两个方法,分别是 paintshouldRepaint,我们画笔实现的所有内容均在 paint里,而shouldRepaint是用来判断是否重绘画笔的,接下来让我们绘制一个带阴影的圆

2. 绘制外层阴影

paint 方法中,首先绘制一个带阴影的圆,这个圆位于底层,提供立体效果。

// 绘制第一个圆
paint.shader = const LinearGradient(begin: Alignment.topCenter,end: Alignment.bottomCenter,colors: [Color(0xFF494949), Color(0xFF494949)],
).createShader(Rect.fromCircle(center: Offset(size.width / 2, size.height / 2),radius: size.width / 2));
paint.style = PaintingStyle.fill;
paint.color = Colors.black.withOpacity(0.6);
canvas.drawCircle(Offset(size.width / 2, size.height / 2),circleSize,paint,
);

将上面代码编写完成后,你将会获得一个灰色的圆形,如下图:

image

3. 绘制底层绿色圆

接下来,我们绘制一个稍小一点的绿色圆,作为底层背景。

// 绘制第二个圆
paint.shader = null;
paint.color = bottomColor;
canvas.drawCircle(Offset(size.width / 2, size.height / 2),circleSize - 1.5298,paint,
);

完成上面代码编写,你将会得到一个比外层阴影圆小一些的一个圆形,如下图:

image

4. 绘制顶层深绿色圆

然后,绘制另一个更小的深绿色圆,进一步增加层次感。

// 绘制第三个圆
paint.color = topColor;
canvas.drawCircle(Offset(size.width / 2, size.height / 2),circleSize - 7.9043,paint,
);

效果如下:

image

5. 绘制中间椭圆形的光效

接下来,使用渐变绘制一个椭圆形的光效,增强立体感。

// 绘制椭圆形
paint.shader = LinearGradient(begin: Alignment.topCenter,end: Alignment.bottomCenter,colors: [Colors.white, Colors.white.withOpacity(0)],
).createShader(Rect.fromLTWH(0, 8.4668, size.width, 65.7849));
canvas.drawOval(Rect.fromCenter(center: Offset(size.width / 2, size.height / 2 + 2.5498),width: circleSize * 1.87,height: circleSize * 1.73),paint,
);

image

如上图,我们设置了一个椭圆形白色渐变的形状,这是一个很重要的效果,主要实现一个内阴影的效果实现,增强立体感。但是很明显,它的效果太白了,和设计的效果差距巨大。显然不是我们想要的效果,但实际上解决方案也很简单,但有个前提,每个圆的效果都需要使用同一个画笔。先前我做这个效果的时候,是每个圆都创建一个新的画笔,因此无法实现,不过当前文章使用的都是同一个画笔。

在发现画笔实现的效果和设计图实现的效果的区别后,我想到了PS中有一个叫图层混合的效果,我想设计图应该也是这么实现的,就查了下画笔是否有这个功能,很幸运的是,确实有这么一个功能,我们只需要在画出这个椭圆形后,添加下面这行代码到canvas.drawOval之前

paint.blendMode = BlendMode.overlay;

这段代码指定了绘制时使用的混合模式。以下是对这段代码的详细解释:

混合模式(BlendMode)

混合模式决定了在绘制图形时,如何将新绘制的内容与已有的内容进行混合。在 Flutter 中,BlendMode 枚举类提供了多种混合模式选项,BlendMode.overlay 是其中一种。

BlendMode.overlay 的工作原理

BlendMode.overlay 结合了 BlendMode.multiplyBlendMode.screen 的效果。具体来说,当底色比中性灰(50% 灰色)暗时,overlay 使用 BlendMode.multiply;当底色比中性灰亮时,overlay 使用 BlendMode.screen。这种效果通常用于创建高对比度和富有细节的图像。

在你的代码中,设置 paint.blendMode = BlendMode.overlay; 意味着在绘制椭圆形光效时,颜色将与底层颜色混合,产生亮部更亮、暗部更暗的效果,从而增强立体感和光泽效果。

实际效果

在绘制中,BlendMode.overlay 使得椭圆形光效部分的白色渐变与底层的绿色圆形混合。这种混合效果不会完全覆盖底色,而是根据底色的亮度调整新颜色的亮度,从而产生更加自然和生动的光效。

image

6. 绘制闪电符号

设置闪电尺寸

首先,我们设置闪电符号的尺寸,使其相对于最小圆的半径进行缩放。

要计算闪电符号路径中的各个点,我们需要根据实际的图形形状定义每个点的位置,并通过数学公式将其缩放和定位。以下是闪电路径的构造步骤和计算公式。

闪电符号的几何构造

我们将闪电符号视为由一系列点和线段组成的多边形。每个点的坐标可以通过比例缩放来确定。

闪电符号的比例数据

为了构建闪电路径,我们需要定义每个点的相对位置。假设闪电符号的原始尺寸高度为 H,宽度为 W。通过以下公式可以计算每个点的位置:

  1. 顶点 A:顶部点,位于中心上方

    • ( A_x = \frac{6.5847}{23.0308} \times lightningSize )
    • ( A_y = -lightningSize )
  2. 顶点 B:左下点

    • ( B_x = -\frac{11.0841}{23.0308} \times lightningSize )
    • ( B_y = \frac{3.8118}{23.0308} \times lightningSize )
  3. 顶点 C:右上点

    • ( C_x = -\frac{0.7198}{23.0308} \times lightningSize )
    • ( C_y = \frac{3.8118}{23.0308} \times lightningSize )
  4. 顶点 D:左上点

    • ( D_x = -\frac{5.8193}{23.0308} \times lightningSize )
    • ( D_y = \frac{24.3206}{23.0308} \times lightningSize )
  5. 顶点 E:右下点

    • ( E_x = \frac{11.6842}{23.0308} \times lightningSize )
    • ( E_y = -\frac{2.9255}{23.0308} \times lightningSize )
  6. 顶点 F:右中点

    • ( F_x = \frac{1.6576}{23.0308} \times lightningSize )
    • ( F_y = -\frac{2.9255}{23.0308} \times lightningSize )

闪电符号的路径计算公式

根据上述顶点的相对位置,我们可以计算出每个点的实际坐标,并绘制闪电路径。


// 绘制闪电路径
double lightningSize = (circleSize - 7.9043) / 1.5; // 使闪电比第二个圆小Path path = Path();
path.moveTo(size.width / 2 + 6.5847 * lightningSize / 23.0308,size.height / 2 - 23.0308 * lightningSize / 23.0308,
);
path.lineTo(size.width / 2 - 11.0841 * lightningSize / 23.0308,size.height / 2 + 3.8118 * lightningSize / 23.0308,
);
path.lineTo(size.width / 2 - 0.7198 * lightningSize / 23.0308,size.height / 2 + 3.8118 * lightningSize / 23.0308,
);
path.lineTo(size.width / 2 - 5.8193 * lightningSize / 23.0308,size.height / 2 + 24.3206 * lightningSize / 23.0308,
);
path.lineTo(size.width / 2 + 11.6842 * lightningSize / 23.0308,size.height / 2 - 2.9255 * lightningSize / 23.0308,
);
path.lineTo(size.width / 2 + 1.6576 * lightningSize / 23.0308,size.height / 2 - 2.9255 * lightningSize / 23.0308,
);
path.close();Paint lightningPaint = Paint()..color = Colors.white.withOpacity(0.8)..style = PaintingStyle.fill;canvas.drawPath(path, lightningPaint);

总结

通过定义闪电符号的顶点比例,并将其转换为实际坐标,我们可以绘制出一个相对固定比例的闪电符号。这样的方法允许我们在不同大小的圆中绘制相同比例的闪电图标。上述公式通过比例缩放和位置调整,确保闪电符号在中心对称的位置。
最终效果如下:

image

完整代码

class ChargePainter extends CustomPainter {final Color bottomColor;final Color topColor;ChargePainter({this.bottomColor = const Color(0xFF1ACC2C),this.topColor = const Color(0xFF1FA22C),});@overridevoid paint(Canvas canvas, Size size) {Paint paint = Paint();double circleSize = size.width / 2;// 绘制第一个圆paint.shader = const LinearGradient(begin: Alignment.topCenter,end: Alignment.bottomCenter,colors: [Color(0xFF494949), Color(0xFF494949)],).createShader(Rect.fromCircle(center: Offset(size.width / 2, size.height / 2),radius: size.width / 2));paint.style = PaintingStyle.fill;paint.color = Colors.black.withOpacity(0.6);canvas.drawCircle(Offset(size.width / 2, size.height / 2),circleSize,paint,);// 绘制第二个圆paint.shader = null;paint.color = bottomColor;canvas.drawCircle(Offset(size.width / 2, size.height / 2),circleSize - 1.5298,paint,);// 绘制第三个圆paint.color = topColor;canvas.drawCircle(Offset(size.width / 2, size.height / 2),circleSize - 7.9043,paint,);// 绘制椭圆形paint.shader = LinearGradient(begin: Alignment.topCenter,end: Alignment.bottomCenter,colors: [Colors.white, Colors.white.withOpacity(0)],).createShader(Rect.fromLTWH(0, 8.4668, size.width, 65.7849));paint.blendMode = BlendMode.overlay;canvas.drawOval(Rect.fromCenter(center: Offset(size.width / 2, size.height / 2 + 2.5498),width: circleSize * 1.87,height: circleSize * 1.73),paint,);// 绘制闪电路径double lightningSize = (circleSize - 7.9043) / 1.5; // 使闪电比第二个圆小Path path = Path();path.moveTo(size.width / 2 + 6.5847 * lightningSize / 23.0308,size.height / 2 - 23.0308 * lightningSize / 23.0308,);path.lineTo(size.width / 2 - 11.0841 * lightningSize / 23.0308,size.height / 2 + 3.8118 * lightningSize / 23.0308,);path.lineTo(size.width / 2 - 0.7198 * lightningSize / 23.0308,size.height / 2 + 3.8118 * lightningSize / 23.0308,);path.lineTo(size.width / 2 - 5.8193 * lightningSize / 23.0308,size.height / 2 + 24.3206 * lightningSize / 23.0308,);path.lineTo(size.width / 2 + 11.6842 * lightningSize / 23.0308,size.height / 2 - 2.9255 * lightningSize / 23.0308,);path.lineTo(size.width / 2 + 1.6576 * lightningSize / 23.0308,size.height / 2 - 2.9255 * lightningSize / 23.0308,);path.close();Paint lightningPaint = Paint()..color = Colors.white.withOpacity(0.8)..style = PaintingStyle.fill;canvas.drawPath(path, lightningPaint);}@overridebool shouldRepaint(covariant ChargePainter oldDelegate) {return oldDelegate.bottomColor != bottomColor || oldDelegate.topColor != topColor;}
}

通过这种方式,不仅提高了代码的可扩展性和性能,还使我们能够轻松适应不同的设计需求和状态变化。希望这篇教程能为大家的Flutter开发提供帮助。

愿君多采撷,莫负好时光。 技术若有涯,勤学步步芳。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/776128.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IDL根据Landsat QA波段去云处理【代码】

IDL根据Landsat QA波段去云处理【代码】 ​ landsat QA波段(质量评估波段)是Landsat卫星影像数据中的一个特殊波段,他在Landsat5-9的每个产品中都存在。虽然我们常用的Landsat影像数据有B1-B7波段,但QA波段并不是其中之一。它可以反映出云、云阴影、雪等类别的像素,常…

zabbix应用教程:基于Nginx页面响应的日志监控用例

作者 乐维社区(forum.lwops.cn)许远 背景:某公司基于 Nginx 服务器搭建的网站,需要监控页面响应耗时的数据,因此该公司搭建了zabbix开源监控系统,当监控到页面响应时间超过3000ms阈值时,就进行告警通知。本文将通过日志关键字的监控来实现对页面响应时间感知,示例Zabbi…

【折腾记录】Ubuntu24.04LTS下安装Windows版微信

最近装了Win11和Ubuntu双系统,为了能更方便地和朋友交流,遂决定在Ubuntu下安装微信。 首先要去网上找教程,经过一番搜索,正当我在wine和deepin-wine之间犹豫不定之时,忽然发现了GitHub上的这个仓库zq1997/deepin-wine 据其README描述:deepin-wine环境与应用在Debian/Ubun…

cors-filter过滤器解决跨域问题

https://www.cnblogs.com/fanshuyao/cors-filter为第三方组件。 一、官网地址 http://software.dzhuvinov.com/cors-filter.html二、Springboot使用cors-filter 1、引入依赖<dependency><groupId>com.thetransactioncompany</groupId><artifactId>cors…

ThinkPHP6之Excel解析

PhpSpreadsheet解析Excel文件 安装 PhpSpreadsheet 通过 Composer 安装了 PhpSpreadsheet: composer require phpoffice/phpspreadsheet控制器 ExcelController <?phpnamespace app\controller;use think\facade\Db; use think\facade\Request; use think\facade\View; us…

腾讯云数据库认证官方的考试费是多少钱?

腾讯云的认证项目很多,包括云计算、大数据、人工智能等多个技术领域方向的认证路径,每个方向包括工程师(Associate)、高级工程师(Professional)、专家(Expert)三个不同的等级。 对于数据库方面,腾讯云的这三个级别的考试费用分别是: TCCA工程师:1200元 TCCP高级工程师:18…

P3043 [USACO12JAN] Bovine Alliance G 题解

P3043 [USACO12JAN] Bovine Alliance G 题目传送门 思路 首先分情况讨论每种联通块的可能,有三种不同的情况会对答案 \(ans\) 产生不同的贡献。 联通块有环如图,因为每条边都有要有归属,所以环上的边只能全都顺时针或逆时针属于某个点,且不在环上的点仅有一种可能。 因此该…

组合数学学习笔记(持续完善中)

基础知识 一、加法原理 完成某个工作有 \(n\) 类办法,第 \(i\) 类办法有 \(a_i\) 种,则完成此工作的方案数有 \(\sum\limits _{i=1}^n a_i\) 种。 二、乘法原理 完成某个工作有 \(n\) 个步骤,第 \(i\) 个步骤有 \(b_i\) 种,则完成此工作的方案数有 \(\prod\limits _{i=1}^n…

Apple Safari 17.6 - macOS 专属浏览器 (独立安装包下载)

Apple Safari 17.6 - macOS 专属浏览器 (独立安装包下载)Apple Safari 17.6 - macOS 专属浏览器 (独立安装包下载) 适用于 macOS Ventura 和 macOS Monterey 的 Safari 浏览器 17 请访问原文链接:https://sysin.org/blog/apple-safari-17/,查看最新版。原创作品,转载请保留出…

Gartner 魔力象限:安全信息和事件管理 (SIEM) 2024

Gartner Magic Quadrant for Security Information and Event Management 2024Gartner Magic Quadrant for Security Information and Event Management 2024 Gartner 魔力象限:安全信息和事件管理 2024 请访问原文链接:https://sysin.org/blog/gartner-magic-quadrant-siem-…

Metasploit Pro 4.22.2-2024072501 (Linux, Windows) - 专业渗透测试框架

Metasploit Pro 4.22.2-2024072501 (Linux, Windows) - 专业渗透测试框架Metasploit Pro 4.22.2-2024072501 (Linux, Windows) - 专业渗透测试框架 Rapid7 Penetration testing, release Jul 25, 2024 请访问原文链接:https://sysin.org/blog/metasploit-pro-4/,查看最新版。…

C#中常用集合类型

原文:C#中常用集合类型 - Y00 - 博客园 (cnblogs.com)在C#中,集合是用于存储和操作一组数据项的数据结构。这些集合通常位于 System.Collections 和 System.Collections.Generic 命名空间中。下面我将概述C#中几种常用的集合类型及其特点: 1. System.Collections 命名空间中…