Flutter笔记:绘图示例 - 一个简单的(Canvas )时钟应用
作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263
邮箱 :291148484@163.com
本文地址:https://blog.csdn.net/qq_28550263/article/details/134341545
这一期带来一点,简单、轻松又好玩的活,使用Flutter绘图实现一个时钟应用。
目 录
- 1. 主要知识点介绍
- 2. 整体步骤
- 2.1 有状态时钟类 Clock
- 2.2 时钟类的状态类 _ClockState
- 2.3 Flutter 绘图器类 ClockPainter -> CustomPainter
- 2.4 放在一个页面脚手架中
- 3. 代码实现
- 3.1 有状态的时钟类
- 3.3 时钟类的状态类
- 3.3 绘图器类
- 中心点和半径
- 刻度线的位置
- 时针、分针和秒针的位置
- 关于 math 库
- 关于 Timer
- 4. 效果展示
- F. 完整代码
1. 主要知识点介绍
-
Flutter 绘图 :CustomPainter是一个可以在Canvas上进行自定义绘制的类。我们创建了一个ClockPainter类,继承自CustomPainter,并在paint方法中实现了时钟的绘制逻辑。
-
Timer:这是一个可以在一定时间间隔后执行回调的类。我们使用Timer来每秒更新一次时钟的状态,从而实现指针的移动。
-
DateTime:这是一个日期和时间的类,我们使用它来获取当前的时间。
-
Paint:这是一个画笔的类,我们使用它来设置绘制时的颜色、笔触宽度等属性。
-
Offset:这是一个表示二维向量的类,我们使用它来表示点的坐标。
2. 整体步骤
2.1 有状态时钟类 Clock
首先,我们创建了一个Clock类。它是一个StatefulWidget,因为我们需要一个可以改变状态的Widget来表示时钟。时钟的状态(当前时间)需要不断更新。
2.2 时钟类的状态类 _ClockState
在Clock类的状态类中,我们设置了一个每秒触发一次的定时器。每次定时器触发时,我们都会调用setState方法来更新状态,从而触发界面的重新绘制。
2.3 Flutter 绘图器类 ClockPainter -> CustomPainter
创建了一个继承自CustomPainter的ClockPainter类,用于在Canvas上进行自定义绘制。在ClockPainter的paint方法中,我们实现了时钟的绘制逻辑。接着:
-
在paint方法中,我们首先绘制了时钟的表盘。我们使用了drawCircle方法来绘制一个圆形的表盘,然后使用了一个循环来绘制表盘上的刻度。
-
接下来,我们绘制了时钟的指针。我们使用了DateTime类来获取当前的时间,然后根据当前的小时、分钟和秒数来计算指针的位置。我们使用了正弦和余弦函数来计算指针的位置,因为指针的移动可以看作是在单位圆上的旋转。
-
最后,每当定时器触发时,我们都会更新当前的时间,并触发界面的重新绘制。在每次绘制时,我们都会根据当前的时间来绘制指针的位置,从而实现指针的移动。
2.4 放在一个页面脚手架中
class ClockPage extends StatelessWidget {const ClockPage({super.key});Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('时钟'),),body: const Center(child: Padding(padding: EdgeInsets.all(20),child: Clock(),),),);}
}
3. 代码实现
3.1 有状态的时钟类
class Clock extends StatefulWidget {const Clock({super.key});State<Clock> createState() => _ClockState();
}
3.3 时钟类的状态类
class _ClockState extends State<Clock> {late Timer _timer;void initState() {super.initState();_timer =Timer.periodic(const Duration(seconds: 1), (timer) => setState(() {})); // 每秒更新一次状态,重新绘制}void dispose() {_timer.cancel(); // 销毁时,取消定时器super.dispose();}Widget build(BuildContext context) {return AspectRatio(aspectRatio: 1,child: CustomPaint(painter: ClockPainter(DateTime.now()), // 使用自定义的ClockPainter进行绘制),);}
}
3.3 绘图器类
class ClockPainter extends CustomPainter {final DateTime dateTime;ClockPainter(this.dateTime);void paint(Canvas canvas, Size size) {final centerX = size.width / 2; // 计算画布中心点的X坐标final centerY = size.height / 2; // 计算画布中心点的Y坐标final center = Offset(centerX, centerY); // 画布中心点final radius = min(centerX, centerY); // 计算画布的半径,取宽和高中的最小值final paint = Paint()..strokeWidth = 10; // 创建画笔,设置笔触宽度为10// 画表盘paint.color = Colors.black; // 设置画笔颜色为黑色paint.style = PaintingStyle.stroke; // 设置画笔样式为描边canvas.drawCircle(center, radius, paint); // 在画布上画一个圆形的表盘// 画刻度const tickWidth = 2.0; // 刻度线的宽度paint.strokeWidth = tickWidth; // 设置画笔宽度为刻度线的宽度for (var i = 0; i < 60; i++) { // 循环画60个刻度线var tickLength = i % 5 == 0 ? 15.0 : 5.0; // 如果是5的倍数,则刻度线长度为15,否则为5var tickX1 = centerX + radius * cos(i * 6 * pi / 180); // 计算刻度线起点的X坐标var tickY1 = centerY + radius * sin(i * 6 * pi / 180); // 计算刻度线起点的Y坐标var tickX2 = centerX + (radius - tickLength) * cos(i * 6 * pi / 180); // 计算刻度线终点的X坐标var tickY2 = centerY + (radius - tickLength) * sin(i * 6 * pi / 180); // 计算刻度线终点的Y坐标canvas.drawLine(Offset(tickX1, tickY1), Offset(tickX2, tickY2), paint); // 在画布上画刻度线}// 画时针final hourHandX = centerX +radius *0.4 *cos((dateTime.hour * 30 + dateTime.minute * 0.5) * pi / 180); // 计算时针的X坐标final hourHandY = centerY +radius *0.4 *sin((dateTime.hour * 30 + dateTime.minute * 0.5) * pi / 180); // 计算时针的Y坐标paint.color = Colors.red; // 设置画笔颜色为红色canvas.drawLine(center, Offset(hourHandX, hourHandY), paint); // 在画布上画时针// 画分针final minuteHandX = centerX +radius *0.6 *cos((dateTime.minute * 6 + dateTime.second * 0.1) * pi / 180); // 计算分针的X坐标final minuteHandY = centerY +radius *0.6 *sin((dateTime.minute * 6 + dateTime.second * 0.1) * pi / 180); // 计算分针的Y坐标paint.color = Colors.green; // 设置画笔颜色为绿色canvas.drawLine(center, Offset(minuteHandX, minuteHandY), paint); // 在画布上画分针// 画秒针final secondHandX =centerX + radius * 0.8 * cos((dateTime.second * 6) * pi / 180); // 计算秒针的X坐标final secondHandY =centerY + radius * 0.8 * sin((dateTime.second * 6) * pi / 180); // 计算秒针的Y坐标paint.color = Colors.blue; // 设置画笔颜色为蓝色canvas.drawLine(center, Offset(secondHandX, secondHandY), paint); // 在画布上画秒针}bool shouldRepaint(ClockPainter oldDelegate) {return dateTime != oldDelegate.dateTime; // 当时间改变时,重新绘制}
}
在 paint 方法中,首先计算了画布的中心点和半径。然后创建了一个 Paint 对象,用于设置绘制时的样式,如颜色、笔触宽度等。接下来,使用 drawCircle 方法绘制了表盘,然后通过一个循环绘制了 60 个刻度线。然后,根据当前的时间(dateTime)计算了时针、分针和秒针的位置,并使用 drawLine 方法将它们绘制到画布上。
shouldRepaint 方法决定了当新的 CustomPainter 对象与旧的 CustomPainter 对象比较时,是否需要重新绘制。在这个例子中,只有当时间改变时,才需要重新绘制,所以 shouldRepaint 方法返回了dateTime != oldDelegate.dateTime。
中心点和半径
中心点是通过取画布宽度和高度的一半得到的。半径是画布宽度和高度中的最小值的一半。
刻度线的位置
我们使用了一个循环来绘制60个刻度线。每个刻度线的位置是通过计算其在单位圆上的角度得到的。我们使用了余弦(cos)和正弦(sin)函数来计算刻度线两端的坐标。这是因为单位圆上的点的坐标可以通过角度和半径来计算。
时针、分针和秒针的位置
我们使用了余弦和正弦函数来计算时针、分针和秒针的位置。这是因为指针的移动可以看作是在单位圆上的旋转。我们根据当前的时间(小时、分钟和秒)来计算指针的角度,然后使用余弦和正弦函数来计算指针的坐标。
- 时针的角度是 (dateTime.hour * 30 + dateTime.minute * 0.5) * pi / 180。这是因为一小时对应30度(360度/12小时=30度),而一分钟对应0.5度(30度/60分钟=0.5度)。
- 分针的角度是 (dateTime.minute * 6 + dateTime.second * 0.1) * pi / 180。这是因为一分钟对应6度(360度/60分钟=6度),而一秒对应0.1度(6度/60秒=0.1度)。
- 秒针的角度是 (dateTime.second * 6) * pi / 180。这是因为一秒对应6度(360度/60秒=6度)。
注意,我们在计算角度时,需要将其从度转换为弧度,因为cos和sin函数接受的参数是弧度。我们通过乘以pi / 180来进行转换。
关于 math 库
在这段代码中,我们使用了Dart的math库,它提供了一些基本的数学函数和常量。需要单独导入:
import 'dart:math';
-
min函数:min函数接受两个参数,并返回其中的最小值。在这段代码中,我们使用min函数来计算画布的半径,它是画布宽度和高度中的最小值的一半。
-
cos函数和sin函数:cos函数和sin函数是三角函数,它们接受一个角度(以弧度为单位)作为参数,并返回该角度的余弦值和正弦值。在这段代码中,我们使用cos函数和sin函数来计算时钟刻度线和指针的位置。
-
pi常量:pi是一个表示圆周率π的常量。在这段代码中,我们使用pi常量来将角度从度转换为弧度,因为cos函数和sin函数接受的参数是弧度。
-
乘法和除法运算:我们使用了乘法运算(*)和除法运算(/)来进行一些基本的数学计算,如计算画布的中心点和半径,计算刻度线和指针的位置等。
关于 Timer
Timer是Dart的dart:async库中的一个类,它可以在给定的持续时间(Duration)之后,或者每隔给定的持续时间,触发一个回调函数。使用 Timer 需要导入 ‘dart:async’ 库:
import 'dart:async';
在这个Flutter时钟应用中,我们使用了Timer的periodic构造函数来创建一个周期性的定时器。这个定时器每隔一秒(Duration(seconds: 1))就会触发一个回调函数。
这个回调函数是一个匿名函数,它调用了setState方法来更新状态。这会触发界面的重新绘制,从而更新时钟的显示。
当我们不再需要定时器时,我们可以调用cancel方法来取消定时器。在这个应用中,我们在dispose方法中调用了cancel方法,以确保当Widget被销毁时,定时器也被取消。例如
Timer _timer = Timer.periodic(Duration(seconds: 1), (timer) {// 这个回调函数会在每隔一秒时被触发print('Timer ticked!');
});// 当我们不再需要定时器时,我们可以取消它
_timer.cancel();
4. 效果展示
代码效果的 GIF 图展示如下:

F. 完整代码
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';class ClockPage extends StatelessWidget {const ClockPage({super.key});Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Canvas 时钟'),),body: const Center(child: Padding(padding: EdgeInsets.all(20),child: Clock(),),),);}
}class ClockPainter extends CustomPainter {final DateTime dateTime;ClockPainter(this.dateTime);void paint(Canvas canvas, Size size) {final centerX = size.width / 2; // 计算画布中心点的X坐标final centerY = size.height / 2; // 计算画布中心点的Y坐标final center = Offset(centerX, centerY); // 画布中心点final radius = min(centerX, centerY); // 计算画布的半径,取宽和高中的最小值final paint = Paint()..strokeWidth = 10; // 创建画笔,设置笔触宽度为10// 画表盘paint.color = Colors.black; // 设置画笔颜色为黑色paint.style = PaintingStyle.stroke; // 设置画笔样式为描边canvas.drawCircle(center, radius, paint); // 在画布上画一个圆形的表盘// 画刻度const tickWidth = 2.0; // 刻度线的宽度paint.strokeWidth = tickWidth; // 设置画笔宽度为刻度线的宽度for (var i = 0; i < 60; i++) {// 循环画60个刻度线var tickLength = i % 5 == 0 ? 15.0 : 5.0; // 如果是5的倍数,则刻度线长度为15,否则为5var tickX1 = centerX + radius * cos(i * 6 * pi / 180); // 计算刻度线起点的X坐标var tickY1 = centerY + radius * sin(i * 6 * pi / 180); // 计算刻度线起点的Y坐标var tickX2 = centerX +(radius - tickLength) * cos(i * 6 * pi / 180); // 计算刻度线终点的X坐标var tickY2 = centerY +(radius - tickLength) * sin(i * 6 * pi / 180); // 计算刻度线终点的Y坐标canvas.drawLine(Offset(tickX1, tickY1), Offset(tickX2, tickY2), paint); // 在画布上画刻度线}// 画时针final hourHandX = centerX +radius *0.4 *cos((dateTime.hour * 30 + dateTime.minute * 0.5) *pi /180); // 计算时针的X坐标final hourHandY = centerY +radius *0.4 *sin((dateTime.hour * 30 + dateTime.minute * 0.5) * pi / 180);paint.color = Colors.red; // 设置画笔颜色为红色canvas.drawLine(center, Offset(hourHandX, hourHandY), paint); // 在画布上画时针// 画分针final minuteHandX = centerX +radius *0.6 *cos((dateTime.minute * 6 + dateTime.second * 0.1) *pi /180); // 计算分针的X坐标final minuteHandY = centerY +radius *0.6 *sin((dateTime.minute * 6 + dateTime.second * 0.1) *pi /180); // 计算分针的Y坐标paint.color = Colors.green; // 设置画笔颜色为绿色canvas.drawLine(center, Offset(minuteHandX, minuteHandY), paint); // 在画布上画分针// 画秒针final secondHandX = centerX +radius * 0.8 * cos((dateTime.second * 6) * pi / 180); // 计算秒针的X坐标final secondHandY = centerY +radius * 0.8 * sin((dateTime.second * 6) * pi / 180); // 计算秒针的Y坐标paint.color = Colors.blue; // 设置画笔颜色为蓝色canvas.drawLine(center, Offset(secondHandX, secondHandY), paint); // 在画布上画秒针}bool shouldRepaint(ClockPainter oldDelegate) {return dateTime != oldDelegate.dateTime; // 当时间改变时,重新绘制}
}class Clock extends StatefulWidget {const Clock({super.key});State<Clock> createState() => _ClockState();
}class _ClockState extends State<Clock> {late Timer _timer;void initState() {super.initState();_timer = Timer.periodic(const Duration(seconds: 1),(timer) => setState(() {})); // 每秒更新一次状态,重新绘制}void dispose() {_timer.cancel(); // 销毁时,取消定时器super.dispose();}Widget build(BuildContext context) {return AspectRatio(aspectRatio: 1,child: CustomPaint(painter: ClockPainter(DateTime.now()), // 使用自定义的ClockPainter进行绘制),);}
}
相关文章:
Flutter笔记:绘图示例 - 一个简单的(Canvas )时钟应用
Flutter笔记 绘图示例 - 一个简单的(Canvas )时钟应用 作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 邮箱 :291148484163.com 本文地址:https://blog.csdn.net/qq_2855…...
Bard和ChatGPT的一些比较
Bard和ChatGPT的一些比较 2023.11.8版权声明:本文为博主chszs的原创文章,未经博主允许不得转载。 选择正确的自动文本生成工具对企业至关重要。本文将详细分析谷歌 Bard 和 ChatGPT 的优缺点,以帮助企业做出明智的选择。无论企业关注的是客…...
centos7安装Nexus(Maven私服)与配置使用教程
之前有位大佬问我,他说有个第三方的Jar包,在idea导出库中使用,现在要部署上线测试,要如何导进去打包。 我说,不用那么麻烦,搞个Nexus私服,将Jar上传上去,然后配置Maven的setting文件…...
Azure 机器学习 - 有关为 Azure 机器学习配置 Kubernetes 群集的参考
目录 受支持的 Kubernetes 版本和区域建议的资源计划ARO 或 OCP 群集的先决条件禁用安全增强型 Linux (SELinux)ARO 和 OCP 的特权设置 收集的日志详细信息Azure 机器学习作业与自定义数据存储连接支持的 Azure 机器学习排斥和容许最佳实践 通过 HTTP 或 HTTPS 将其他入口控制器…...
使用微信小程序控制蓝牙小车(微信小程序端)
目录 使用接口界面效果界面设计界面逻辑设计 使用接口 微信小程序官方开发文档 接口说明wx.openBluetoothAdapter初始化蓝牙模块wx.closeBluetoothAdapter关闭蓝牙模块(调用该方法将断开所有已建立的连接并释放系统资源)wx.startBluetoothDevicesDiscovery开始搜寻附近的蓝牙…...
【react hook】react hook组件中,在forEach中使用async/awati进行异步操作,为什么后面代码没有等待直接同步运行了呢?
这是因为 forEach 方法不会等待 async/await 异步操作的完成。forEach 方法是一种同步的方法,它会在每个迭代内部同步执行一个回调函数。当遇到 await 时,会立即暂停执行,但是 forEach 方法不会等待回调函数中的 await 异步操作完成ÿ…...
高斯过程回归 | GPR高斯过程回归
高斯过程回归(Gaussian Process Regression, GPR)是一种强大的非参数回归方法,它通过假设数据是从一个高斯过程中生成的来预测新的数据点。 高斯过程是一种定义在连续输入空间上的随机过程,其中任何有限集合的观测值都呈多变量高斯分布。 实现GPR的Python代码import numpy …...
[autojs]逍遥模拟器和vscode对接
第一步:启动autojs服务 第二步:去cmd查看ip地址,输入ipconfig 第三步:打开逍遥模拟器中的sutojs-左上角- 连接电脑,然后输入WLAN或者其他ip也行,根据自己电脑实际情况确认 此时vscode显示连接成功。我们写…...
Docker 安装与优化
一、安装Docker 1、关闭防火墙 systemctl stop firewalld systemctl disable firewalld setenforce 02、安装依赖包 yum -y install yum-utils device-mapper-persistent-data lvm2#解释 yum-utils #提供了yum-config-manager工具 device mapper #是linux内核中支持逻辑卷…...
Wix使用velo添加Google ads tag并在form表单提交时向谷歌发送事件
往head里加代码时,不能看谷歌的代码,要看wix的代码,不然必定踩坑 https://support.wix.com/en/article/tracking-google-ads-conversions-using-wix-custom-code 这里的代码才对,因为wix搞了个velo,这个velo很傻x&am…...
Centos配置邮件发送
在CentOS Linux上配置邮件发送 在这个指南中,我们将讨论如何配置CentOS Linux系统以通过外部邮件服务器发送电子邮件,使用自己的邮件账户进行发送。 第一步:开启SMTP授权码 首先,我们以QQ邮箱为例,需要开启SMTP授权…...
Ubuntu系统使用apt-get管理软件工具
记录一下使用Ubuntu系统的apt-get管理软件工具 先查看一下系统的版本,可以看到这里使用的是Ubuntu20.04版本,版本代号focal rootmyw:~# uname -a Linux myw 5.4.0-70-generic #78-Ubuntu SMP Fri Mar 19 13:29:52 UTC 2021 x86_64 x86_64 x86_64 GNU/L…...
带你走进Cflow (三)·控制符号类型分析
目录 编辑 1、控制符号类型 1.1 语法类 1.2 符号别名 1.3 GCC 初始化 1、控制符号类型 有人也许注意到了输出中奇怪的现象:函数_exit 丢失了,虽然它在源文件中被printdir 调用了两次。这是因为默认情况下 cflow 忽略所有的一下划线开头的符号…...
FPGA UDP RGMII 千兆以太网(3)ODDR
1 xilinx原语 在 7 系列 FPGA 中实现 RGMII 接口需要借助 5 种原语,分别是:IDDR、ODDR、IDELAYE2、ODELAYE2(A7 中没有)、IDELAYCTRL。其中,IDDR和ODDR分别是输入和输出的双边沿寄存器,位于IOB中。IDELAYE2和ODELAYE2,分别用于控制 IO 口输入和输出延时。同时,IDELAYE2 …...
OSG交互:选中场景模型并高亮显示
1、目的 可以在osg视图中选中指定模型实体,并高亮显示。共分为两种,一种鼠标点选,一种框选。 2、鼠标点选 2.1 功能说明 生成两组对象,一组cow对象可以被选中,另一组robot不能被选中;点击cow对象被选中高亮,点击robot被选中不高亮;点击空白处,弹出“select nothing!…...
农业大棚智能化改造升级与远程视频监管方案,助力智慧农业建设发展
一、需求分析 随着现代化技术的发展,农业大棚的智慧化也成为当前备受关注的智慧农业发展手段。利用先进的信息化手段来对农业大棚进行管理,采集和掌握作物的生长状况、作业监督、生态环境等信息数据,实现精准操作、精细管理,远程…...
P6入门:项目初始化2-项目详情之日期Date
前言 使用项目详细信息查看和编辑有关所选项目的详细信息,在项目创建完成后,初始化项目是一项非常重要的工作,涉及需要设置的内容包括项目名,ID,责任人,日历,预算,资金,分类码等等&…...
【ubuntu20.04】win10安装ubuntu20.04双系统
win10安装ubuntu20.04双系统: 【ubuntu20.04】win10安装ubuntu20.04双系统:https://www.bilibili.com/video/BV11k4y1k7Li/?spm_id_from333.999.0.0&vd_source66a67b8a0b3c4e03915bf8b3a6ff9a74 ubuntu与windows双系统时间同步: windows认为&…...
使用ffmpeg 压缩视频
我有一批1080p的视频,在网上播放占用空间太大,需要进行压缩以后再上传,下面是记录一下ffmpeg命令的使用情况 原视频大小:288mb --压缩加修改分辨率 640p ffmpeg -y -i C4995.mp4 -vcodec libx264 -crf 18 -s vga C4995\C4995_2.MP4 -y: 强制覆盖 -i :输入文件 -vcodec lib…...
C# , .netWebApi, WPF 用特性实现类似Java 的Ioc 自动装配@Autowired
写C# 一直很羡慕Java的@Autowired 自动装配. 因为C# 必须手动在Ioc里注册 之前用接口实现了自动注册IOC, 总是觉得美中不足, 毕竟没有真正实现用注解/特性实现自动注入, 这次我们来实现一个用特性注入Ioc的扩展方法. namespace MyCode.BLL.Service.Ioc {/// <summary>/…...
Qwen3-4B-Thinking多场景落地:电商客服+教育答疑+IT文档生成三合一
Qwen3-4B-Thinking多场景落地:电商客服教育答疑IT文档生成三合一 1. 模型简介与核心能力 Qwen3-4B-Thinking-2507-Gemini-2.5-Flash-Distill是一个基于vLLM部署的高效文本生成模型,通过在大约5440万个由Gemini 2.5 Flash生成的token上进行训练…...
ICL8038信号发生器DIY全攻略:从原理图到波形调试(附AD源文件)
ICL8038信号发生器DIY全攻略:从原理图到波形调试 在电子工程领域,信号发生器是实验室和研发工作中不可或缺的基础设备。市面上的专业信号发生器往往价格昂贵,而基于ICL8038芯片的DIY方案,能以极低成本实现实验室级别的多功能波形输…...
从开发机到生产环境:C# 14原生AOT部署Dify客户端的CI/CD流水线设计(GitHub Actions + Azure Pipelines双模板)
第一章:C# 14 原生 AOT 部署 Dify 客户端的全景认知C# 14 原生 AOT(Ahead-of-Time)编译能力在 .NET 9 中正式成熟,为构建轻量、安全、启动极速的 Dify 客户端提供了全新范式。与传统 JIT 或托管发布不同,AOT 编译将 C#…...
MyBatis RowBounds分页踩坑实录:一次线上OOM事故教会我的事
MyBatis分页陷阱:从RowBounds内存泄漏到高效分页实战 凌晨三点,手机突然响起刺耳的报警声。打开监控系统一看,某核心服务的堆内存曲线像坐了火箭一样直线上升,最终触发了OOM崩溃。经过彻夜排查,罪魁祸首竟是项目中一段…...
Linux设备树实战:如何为IMX6ULL开发板定制dts文件(附完整编译流程)
Linux设备树实战:如何为IMX6ULL开发板定制dts文件(附完整编译流程) 在嵌入式Linux开发中,设备树(Device Tree)已经成为硬件描述的标准方式。对于使用NXP i.MX6ULL处理器的开发者来说,掌握设备树…...
AVIF 与 PNG:下一代图像格式如何改变网页视觉与性能
随着互联网对高质量图像和快速加载速度的要求不断提高,图像格式也在不断进化。从早期的 JPEG、PNG,到如今逐渐普及的 WebP 和 AVIF,图像技术正在经历一场深刻的变革。 其中,AVIF 是近年来最受关注的新一代图像格式之一࿰…...
从原理图到后仿真的完整流程:Virtuoso Layout XL + Calibre DRC/LVS/PEX保姆级避坑指南
从原理图到后仿真的完整流程:Virtuoso Layout XL Calibre DRC/LVS/PEX保姆级避坑指南 在集成电路设计领域,从原理图到最终的后仿真验证是一个环环相扣的系统工程。对于刚入行的工程师来说,这个过程往往充满了各种"坑"——从版图绘…...
从零到一:用VuePress/Hexo搭建技术博客时,你必须搞懂的SEO配置(附完整代码)
从零到一:用VuePress/Hexo搭建技术博客时,你必须搞懂的SEO配置(附完整代码) 技术博客不仅是开发者记录学习历程的载体,更是个人品牌的重要展示窗口。但很多开发者发现,即使内容优质,博客流量依然…...
驱动业务闭环的底层逻辑:为什么说 AI Agent 是企业数字化转型的必选项?
站在2026年这个“AI Agent落地元年”的时间节点回看, 企业数字化转型的叙事逻辑已经发生了根本性逆转。 如果说2023年是“大模型元年”,企业还在为Prompt调优而兴奋, 那么2025年到2026年的跨越,则标志着AI从“会聊天”进化到了“能…...
FPGA驱动RGB屏幕时序详解:从VGA原理到480x272 TFT实战调试记录
FPGA驱动RGB屏幕时序详解:从VGA原理到480x272 TFT实战调试记录 当你在调试一块4.3寸RGB TFT屏幕时,是否遇到过这样的场景:FPGA程序烧录后,屏幕要么一片空白,要么显示错位、花屏?这往往源于对时序参数的误解…...
