HarmonyOS7 Canvas 怎么动起来:CanvasAnimationLoop 动画循环详解
文章目录前言这页案例到底讲了什么完整代码angle 是这段动画的发动机startAnimation() 做的其实是“开循环”动画帧开始前为什么一定要 clearRect()旋转为什么要配合 translate() 使用save() 和 restore() 为什么不能少颜色跟着角度变化是个很巧的示范stopAnimation() 不只是停止按钮这套思路还能扩展成什么最容易踩的坑写在最后前言静态图形会画了只能算学到一半。Canvas真正开始有表现力往往是在它动起来之后。旋转、粒子、波纹、仪表盘、加载动画很多你平时觉得“这页面有点高级”的效果本质上都是一帧一帧刷出来的。问题也正出在这里。一旦开始动画Canvas就不再只是“画一张图”而变成“持续重绘一个状态变化中的画面”。这跟前面学静态图形完全不是一个思路。这个案例非常适合做第一步因为它只做了一件事让一个方块旋转起来。这页案例到底讲了什么表面看它在讲旋转动画实际上讲的是一套最基础的Canvas动画循环模型用状态变量保存动画进度用定时器持续更新状态每次重绘前清空旧画面通过坐标变换实现旋转这套模型虽然简单但后面的很多动画都离不开它。完整代码import{DEMO_BG_COLOR,DEMO_CARD_COLOR,DEMO_THEME_COLOR,DEMO_SUBTEXT_COLOR}from./typesEntryComponentstruct CanvasAnimationLoop{StateisShow:booleantrueStateangle:number0privatesettings:RenderingContextSettingsnewRenderingContextSettings(true)privatecontext:CanvasRenderingContext2DnewCanvasRenderingContext2D(this.settings)privatetimerId:number-1StateisAnimating:booleanfalsestartAnimation():void{if(this.isAnimating)returnthis.isAnimatingtruethis.timerIdsetInterval((){this.angle(this.angle5)%360},50)}stopAnimation():void{this.isAnimatingfalseclearInterval(this.timerId)}build(){Column(){if(this.isShow){Scroll(){Column({space:16}){Text(Canvas 动画效果).fontSize(16).fontColor(DEMO_THEME_COLOR).fontWeight(FontWeight.Bold)Column(){Text(旋转动画演示).fontSize(13).fontColor(#4D96FF).margin({bottom:8})Column(){Canvas(this.context).onReady((){constw380consth180this.context.clearRect(0,0,w,h)this.context.save()this.context.translate(w/2,h/2)this.context.rotate(this.angle*Math.PI/180)this.context.fillStylehsl(${this.angle}, 70%, 60%)this.context.fillRect(-30,-30,60,60)this.context.strokeStyle#666666this.context.lineWidth1this.context.beginPath()this.context.arc(0,0,50,0,Math.PI*2)this.context.stroke()this.context.restore()this.context.font12px sans-serifthis.context.fillStyle#999999this.context.fillText(旋转角度:${this.angle}°,10,h-10)}).width(100%).height(180).backgroundColor(#F0F0F0).borderRadius(8)}Row({space:12}){Button(开始动画).onClick((){this.startAnimation()}).fontSize(12).backgroundColor(DEMO_THEME_COLOR)Button(停止动画).onClick((){this.stopAnimation()}).fontSize(12).backgroundColor(#FF6B6B)}.margin({top:8})}.backgroundColor(DEMO_CARD_COLOR).padding(12).borderRadius(12)Column({space:6}){Text(Canvas 动画实现).fontSize(14).fontColor(DEMO_THEME_COLOR).fontWeight(FontWeight.Bold)Text(利用 State 变量驱动重绘).fontSize(12).fontColor(#333333)Text(setInterval 定时更新状态).fontSize(12).fontColor(#333333)Text(每次更新调用 context 重新绘制帧).fontSize(12).fontColor(#333333)Text(.clearRect 清除上一帧内容).fontSize(12).fontColor(#333333)Text(.save/.restore 保存恢复状态).fontSize(12).fontColor(#333333)Text(.translate/rotate/scale 变换矩阵).fontSize(12).fontColor(#333333)Text(使用 requestAnimationFrame 更流畅).fontSize(12).fontColor(#333333)}.width(100%).backgroundColor(#E8F4FD).padding(14).borderRadius(12)}.width(100%)}}Text(CanvasAnimationLoop - setInterval 动画循环).fontSize(12).fontColor(DEMO_SUBTEXT_COLOR).margin({top:12})}.width(100%).height(100%).backgroundColor(DEMO_BG_COLOR).padding(16)}}angle是这段动画的发动机案例里最关键的状态就是这个角度Stateangle:number0它本质上不是“一个数字”而是当前动画帧的描述。只要这个值持续变化画面就会持续变化。所以动画思维的第一步不是先想怎么画而是先想哪一个状态变化能带动画面变化在这个案例里答案就是旋转角度。startAnimation()做的其实是“开循环”startAnimation():void{if(this.isAnimating)returnthis.isAnimatingtruethis.timerIdsetInterval((){this.angle(this.angle5)%360},50)}这段代码有两个点必须看懂。第一if (this.isAnimating) return是防止重复启动。没有这句你多点几次开始按钮就会叠出多个定时器速度和状态都会乱掉。第二this.angle (this.angle 5) % 360是让角度持续增加并循环归零。这样不管动画跑多久数值都不会无限膨胀。动画帧开始前为什么一定要clearRect()案例里每次绘制前都做了这件事this.context.clearRect(0,0,w,h)这是Canvas动画最基本的卫生习惯。因为画布不会自动忘记上一帧。你不清空新的方块会叠在旧方块上最后就会形成拖影或者一团糊掉的痕迹。除非你故意想做残影效果否则绝大多数动画都应该先清屏再重画。旋转为什么要配合translate()使用这段变换代码非常关键this.context.save()this.context.translate(w/2,h/2)this.context.rotate(this.angle*Math.PI/180)this.context.fillRect(-30,-30,60,60)this.context.restore()如果你直接rotate()图形会围绕当前坐标原点转通常不是你想要的效果。这里先translate(w / 2, h / 2)意思是把坐标原点移动到画布中心。然后再旋转方块就会围绕中心点转。最后fillRect(-30, -30, 60, 60)用负坐标把方块中心对齐到新原点。这个小技巧非常常见。save()和restore()为什么不能少这两个方法是状态隔离的关键。因为translate()和rotate()会改变当前坐标系如果你不把变换前的状态保存下来后面画文字、画圆、画别的元素时都会被这个旋转坐标系拖进去。案例里旋转完成后马上restore()就是为了让后面的说明文字继续按照正常坐标绘制。颜色跟着角度变化是个很巧的示范案例用了这句this.context.fillStylehsl(${this.angle}, 70%, 60%)这不是必须的但很聪明。因为它让你看到动画状态不仅能控制位置和旋转也能同时控制颜色变化。这一下就把你的思路打开了。以后做动态进度、状态光效、节奏反馈时完全可以让一个状态同时驱动多个视觉属性。stopAnimation()不只是停止按钮stopAnimation():void{this.isAnimatingfalseclearInterval(this.timerId)}停止动画这件事别轻视。它不仅是交互按钮需要更是资源管理需要。如果页面离开了、组件销毁了动画还在后台跑CPU 和电量都会被白白消耗。真实项目里这部分通常还要结合页面生命周期进一步收口。这套思路还能扩展成什么你一旦理解这个案例就能顺手扩展出很多东西加载转圈动画仪表盘指针摆动粒子漂浮波纹扩散数据高亮闪动区别只在于驱动状态变成了什么重绘逻辑换成了什么图形。最容易踩的坑第一个坑是重复启动多个定时器。第二个坑是没清空上一帧画面越跑越脏。第三个坑是做了坐标变换后忘记restore()后面的元素全歪。第四个坑是页面离开时没停动画性能被偷偷吃掉。写在最后动画的本质从来不是“会动”这么简单而是你是否能稳定地控制一帧一帧的状态变化。这个案例看着小但已经把Canvas动画最关键的几根骨头搭出来了。把它练顺后面无论你做加载、图表、交互反馈都会更有底气。

相关新闻

Java 反射全套常用方法汇总

Java 反射全套常用方法汇总

反射核心围绕 Class、Constructor、Method、Field 四大API,下面分类整理,附带作用与区分要点。前提:获取类字节码对象 1.Class clazz 目标类.class;2. Class clazz Class.forName("全类名");3.对象.getClass()一、Class 类&#…

2026/10/12 21:20:57 阅读更多 →
第80讲:附赠——嵌入式双范式全套可复制Prompt库

第80讲:附赠——嵌入式双范式全套可复制Prompt库

第80讲:附赠——嵌入式双范式全套可复制Prompt库 专栏地址: 嵌入式程序开发实战嵌入式双范式AI编程嵌入式开发必掌握嵌入式求职面试技术资料 前言:为什么需要Prompt模板库? 为什么重要? 通过前面14讲的学习&#x…

2026/10/13 0:15:39 阅读更多 →
第79讲:输出净化——剔除AI废话,只保留纯净C代码

第79讲:输出净化——剔除AI废话,只保留纯净C代码

第79讲:输出净化——剔除AI废话,只保留纯净C代码 专栏地址: 嵌入式程序开发实战嵌入式双范式AI编程嵌入式开发必掌握嵌入式求职面试技术资料 前言:为什么需要净化AI输出? 为什么重要? AI输出通常包含大…

2026/10/12 22:24:02 阅读更多 →

最新新闻

并行编程——MPI 和OpenMPI 基本概念学习(一)

并行编程——MPI 和OpenMPI 基本概念学习(一)

文章大纲引言一、MPI 概述二、MPI基础数据类型1、使用MPI_Type_create_struct创建结构体类型数据类型1.1、在C中定义结构体并确定成员偏移量1.2、创建自定义 MPI 数据类型2、使用MPI_Type_create_subarray创建子数组类型数据类型2.1、确定子数组的形状和位置2.2、创建自定义 MP…

2026/10/14 1:53:15 阅读更多 →
syzkaller 依赖的 errchkjson 静态分析器:json.Marshal 错误处理与类型安全分类详解

syzkaller 依赖的 errchkjson 静态分析器:json.Marshal 错误处理与类型安全分类详解

测试操作系统网络安全 【免费下载链接】syzkaller syzkaller is an unsupervised coverage-guided kernel fuzzer 项目地址: https://gitcode.com/gh_mirrors/syz/syzkaller 点击查看 免费下载 本文围绕 syzkaller 仓库中 vendor 目录下的 errchkjson 文档展开&…

2026/10/14 1:53:15 阅读更多 →
Farm 懒编译(Lazy Compilation)实战指南:让大型项目开发编译从分钟级降到秒级

Farm 懒编译(Lazy Compilation)实战指南:让大型项目开发编译从分钟级降到秒级

前端构建构建工具开发工具 【免费下载链接】farm Extremely fast Vite-compatible web build tool written in Rust 项目地址: https://gitcode.com/gh_mirrors/fa/farm 点击查看 免费下载 Farm 是一款用 Rust 编写的 Vite 兼容 Web 构建工具,其「懒编译…

2026/10/14 1:53:15 阅读更多 →
TEngine 设计模式应用剖析:单例、工厂、观察者与状态机的工业化实现

TEngine 设计模式应用剖析:单例、工厂、观察者与状态机的工业化实现

游戏开发 【免费下载链接】TEngine Unity 商用级别开发框架,原生内置 AI 工作流支持,集成 HybridCLR 高性能热更、Obfuz 代码混淆加固、YooAssets 企业级资源管理方案,构建高效、安全、可扩展的工业化开发底座。 项目地址: https:…

2026/10/14 1:53:15 阅读更多 →
基于Vue和WebSocket的实时聊天系统实战:从轮询到长连接与踩坑记录

基于Vue和WebSocket的实时聊天系统实战:从轮询到长连接与踩坑记录

简介:这是一套基于WebSocket与Vue.js的在线聊天室系统设计Demo,面向前端开发学习者与全栈入门者,演示如何从零搭建支持实时双向通信的群聊与私聊场景。项目功能完整,涵盖私聊、群聊、消息已读与未读、未读消息提醒、聊天文字颜色区…

2026/10/14 1:53:15 阅读更多 →
新能源EPC项目SAP PS手册:从WBS到CJ88的落地指南

新能源EPC项目SAP PS手册:从WBS到CJ88的落地指南

简介:《新能源项目-2017年6月SAP项目SAP PS模块用户操作手册》是一份面向SAP项目实施顾问、新能源企业项目管理人员及信息化运维人员的操作指引,重点解决项目创建、WBS工作任务分解和标准网络复制等PS模块高频操作问题。文档共67页,以单个Wor…

2026/10/14 1:52:15 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →