鸿蒙NEXT开发案例:转盘1W

news/2024/11/14 12:46:27/文章来源:https://www.cnblogs.com/westworldss/p/18537892

【1】引言(完整代码在最后面)

在鸿蒙NEXT系统中,开发一个有趣且实用的转盘应用不仅可以提升用户体验,还能展示鸿蒙系统的强大功能。本文将详细介绍如何使用鸿蒙NEXT系统开发一个转盘应用,涵盖从组件定义到用户交互的完整过程。

【2】环境准备

电脑系统:windows 10

开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806

工程版本:API 12

真机:mate60 pro

语言:ArkTS、ArkUI

【3】难点分析

1. 扇形路径的计算

难点:创建扇形的路径需要精确计算起始点、结束点和弧线参数。尤其是涉及到三角函数的使用,初学者可能会对如何将角度转换为坐标感到困惑。

解决方案:可以通过绘制简单的示意图来帮助理解扇形的构造,并在代码中添加详细注释,解释每一步的计算过程。

2. 动态角度计算

难点:在转盘旋转时,需要根据单元格的比例动态计算每个单元格的角度和旋转角度。这涉及到累加和比例计算,可能会导致逻辑错误。

解决方案:使用数组的 reduce 方法来计算总比例,并在计算每个单元格的角度时,确保逻辑清晰。可以通过单元测试来验证每个单元格的角度是否正确。

3. 动画效果的实现

难点:实现转盘的旋转动画需要对动画的持续时间、曲线和结束后的状态进行管理。初学者可能会对如何控制动画的流畅性和效果感到困惑。

解决方案:可以参考鸿蒙NEXT的动画文档,了解不同的动画效果和参数设置。通过逐步调试,观察动画效果并进行调整。

4. 用户交互的处理

难点:处理用户点击事件,尤其是在动画进行时,如何禁用按钮以防止重复点击,可能会导致状态管理的复杂性。

解决方案:在按钮的点击事件中,使用状态变量(如 isAnimating)来控制按钮的可用性,并在动画结束后恢复按钮的状态。

5. 组件的状态管理

难点:在多个组件之间传递状态(如当前选中的单元格、转盘的角度等)可能会导致状态管理混乱。

解决方案:使用状态管理工具(如 @State 和 @Trace)来确保状态的统一管理,并在需要的地方进行状态更新,保持组件之间的解耦。

【完整代码】

?

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 import { CounterComponent, CounterType } from '@kit.ArkUI'``; // 导入计数器组件和计数器类型 // 定义扇形组件``@Component``struct Sector {``@Prop radius: number; // 扇形的半径``@Prop angle: number; // 扇形的角度``@Prop color: string; // 扇形的颜色 // 创建扇形路径的函数``createSectorPath(radius: number, angle: number): string {``const centerX = radius / 2; // 计算扇形中心的X坐标``const centerY = radius / 2; // 计算扇形中心的Y坐标``const startX = centerX; // 扇形起始点的X坐标``const startY = centerY - radius; // 扇形起始点的Y坐标``const halfAngle = angle / 4; // 计算半个角度 // 计算扇形结束点1的坐标``const endX1 = centerX + radius * Math.cos((halfAngle * Math.PI) / 180);``const endY1 = centerY - radius * Math.sin((halfAngle * Math.PI) / 180); // 计算扇形结束点2的坐标``const endX2 = centerX + radius * Math.cos((-halfAngle * Math.PI) / 180);``const endY2 = centerY - radius * Math.sin((-halfAngle * Math.PI) / 180); // 判断是否为大弧``const largeArcFlag = angle / 2 > 180 ? 1 : 0;``const sweepFlag = 1; // 设置弧线方向为顺时针 // 生成SVG路径命令``const pathCommands =```M${startX} ${startY} A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${endX1} ${endY1} L${centerX} ${centerY} L${endX2} ${endY2} A${radius} ${radius} 0 ${largeArcFlag} ${1 -``sweepFlag} ${startX} ${startY} Z;return` `pathCommands;` `// 返回路径命令} // 构建扇形组件build() {Stack() {// 创建第一个扇形路径Path().width(`${this.radius}px`)` `// 设置宽度为半径.height(${``this``.radius}px) // 设置高度为半径.commands(this.createSectorPath(this.radius,` `this.angle)) // 设置路径命令.fillOpacity(1)` `// 设置填充透明度.fill(this.color) // 设置填充颜色.strokeWidth(0)` `// 设置边框宽度为0.rotate({ angle: this.angle / 4 - 90 });` `// 旋转扇形` `// 创建第二个扇形路径Path().width(`${this.radius}px`)` `// 设置宽度为半径.height(${``this``.radius}px) // 设置高度为半径.commands(this.createSectorPath(this.radius,` `this.angle)) // 设置路径命令.fillOpacity(1)` `// 设置填充透明度.fill(this.color) // 设置填充颜色.strokeWidth(0)` `// 设置边框宽度为0.rotate({ angle: 180 - (this.angle / 4 - 90) }); // 旋转扇形}}}` `// 定义单元格类@ObservedV2class` `Cell {@Trace angle: number = 0; // 扇形的角度@Trace title: string;` `// 当前格子的标题@Trace color: string; // 背景颜色@Trace rotate: number = 0;` `// 在转盘要旋转的角度angleStart: number = 0; // 轮盘所在区间的起始angleEnd: number = 0;` `// 轮盘所在区间的结束proportion: number = 0; // 所占比例 // 构造函数constructor(proportion: number, title: string, color: string) {this.proportion = proportion;` `// 设置比例this.title = title;` `// 设置标题this.color = color;` `// 设置颜色}}` `// 定义转盘组件@Entry@Componentstruct Wheel {@State cells: Cell[] = [];` `// 存储单元格的数组@State wheelWidth: number = 600; // 转盘的宽度@State currentAngle: number = 0;` `// 当前转盘的角度@State selectedName: string = "";` `// 选中的名称isAnimating: boolean = false;` `// 动画状态colorIndex: number = 0; // 颜色索引colorPalette: string[] = [` `// 颜色调色板"#26c2ff","#978efe","#c389fe","#ff85bd","#ff7051","#fea800","#ffcf18","#a9c92a"];` `// 组件即将出现时调用aboutToAppear(): void {// 初始化单元格this.cells.push(new Cell(1, "跑步",` `this.colorPalette[this.colorIndex++ % this.colorPalette.length]));this.cells.push(new Cell(2, "跳绳",` `this.colorPalette[this.colorIndex++ %` `this.colorPalette.length]));this.cells.push(new` `Cell(1,` `"唱歌",` `this.colorPalette[this.colorIndex++ %` `this.colorPalette.length]));this.cells.push(new` `Cell(4,` `"跳舞",` `this.colorPalette[this.colorIndex++ %` `this.colorPalette.length]));` `this.calculateAngles();` `// 计算角度}` `// 计算每个单元格的角度private` `calculateAngles() {// 根据比例计算总比例const totalProportion =` `this.cells.reduce((sum, cell) => sum + cell.proportion, 0);this.cells.forEach(cell => {cell.angle = (cell.proportion * 360) / totalProportion;` `// 计算每个单元格的角度});` `let` `cumulativeAngle = 0;` `// 累计角度this.cells.forEach(cell => {cell.angleStart = cumulativeAngle;` `// 设置起始角度cumulativeAngle += cell.angle;` `// 更新累计角度cell.angleEnd = cumulativeAngle;` `// 设置结束角度cell.rotate = cumulativeAngle - (cell.angle / 2);` `// 计算旋转角度});}` `// 构建转盘组件build() {Column() {Row() {Text('转盘').fontSize(20).fontColor("#0b0e15");` `// 显示转盘标题}.width('100%').height(44).justifyContent(FlexAlign.Center);` `// 设置行的宽度和高度` `// 显示当前状态Text(this.isAnimating ?` `'旋转中'` `: `${this.selectedName}`).fontSize(20).fontColor("#0b0e15").height(40);` `Stack() {Stack() {// 遍历每个单元格并绘制扇形ForEach(this.cells, (cell: Cell) => {Stack() {Sector({ radius: lpx2px(this.wheelWidth) / 2, angle: cell.angle, color: cell.color });` `// 创建扇形Text(cell.title).fontColor(Color.White).margin({ bottom: `${this.wheelWidth / 1.4}lpx` });` `// 显示单元格标题}.width('100%').height('100%').rotate({ angle: cell.rotate });` `// 设置宽度和高度,并旋转});}.borderRadius('50%')` `// 设置圆角.backgroundColor(Color.Gray)` `// 设置背景颜色.width(`${this.wheelWidth}lpx`)` `// 设置转盘宽度.height(`${this.wheelWidth}lpx`)` `// 设置转盘高度``.rotate({ angle:` `this``.currentAngle });` `// 旋转转盘` `// 创建指针``Polygon({ width: 20, height: 10 })``.points([[0, 0], [10, -20], [20, 0]])` `// 设置指针的点``.fill(``"#d72b0b"``)` `// 设置指针颜色``.height(20)` `// 设置指针高度``.margin({ bottom:` `'140lpx'` `});` `// 设置指针底部边距` `// 创建开始按钮``Button(``'开始'``)``.fontColor(``"#c53a2c"``)` `// 设置按钮字体颜色``.borderWidth(10)` `// 设置按钮边框宽度``.borderColor(``"#dd2218"``)` `// 设置按钮边框颜色``.backgroundColor(``"#fde427"``)` `// 设置按钮背景颜色``.width(``'200lpx'``)` `// 设置按钮宽度``.height(``'200lpx'``)` `// 设置按钮高度``.borderRadius(``'50%'``)` `// 设置按钮为圆形``.clickEffect({ level: ClickEffectLevel.LIGHT })` `// 设置点击效果``.onClick(() => {` `// 点击按钮时的回调函数``if` `(``this``.isAnimating) {` `// 如果正在动画中,返回``return``;``}``this``.selectedName =` `""``;` `// 清空选中的名称``this``.isAnimating =` `true``;` `// 设置动画状态为正在动画``animateTo({` `// 开始动画``duration: 5000,` `// 动画持续时间为5000毫秒``curve: Curve.EaseInOut,` `// 动画曲线为缓入缓出``onFinish: () => {` `// 动画完成后的回调``this``.currentAngle %= 360;` `// 保持当前角度在0到360之间``for` `(const cell of` `this``.cells) {` `// 遍历每个单元格``// 检查当前角度是否在单元格的角度范围内``if` `(360 -` `this``.currentAngle >= cell.angleStart && 360 -` `this``.currentAngle <= cell.angleEnd) {``this``.selectedName = cell.title;` `// 设置选中的名称为当前单元格的标题``break``;` `// 找到后退出循环``}``}``this``.isAnimating =` `false``;` `// 设置动画状态为未动画``},``}, () => {` `// 动画进行中的回调``this``.currentAngle += (360 * 5 + Math.floor(Math.random() * 360));` `// 更新当前角度,增加随机旋转``});``});``}` `// 创建滚动区域``Scroll() {``Column() {``// 遍历每个单元格,创建输入框和计数器``ForEach(``this``.cells, (item: Cell, index: number) => {``Row() {``// 创建文本输入框,显示单元格标题``TextInput({ text: item.title })``.layoutWeight(1)` `// 设置输入框占据剩余空间``.onChange((value) => {` `// 输入框内容变化时的回调``item.title = value;` `// 更新单元格标题``});``// 创建计数器组件``CounterComponent({``options: {``type: CounterType.COMPACT,` `// 设置计数器类型为紧凑型``numberOptions: {``label: `当前占比`,` `// 设置计数器标签``value: item.proportion,` `// 设置计数器初始值``min: 1,` `// 设置最小值``max: 100,` `// 设置最大值``step: 1,` `// 设置步长``onChange: (value: number) => {` `// 计数器值变化时的回调``item.proportion = value;` `// 更新单元格的比例``this``.calculateAngles();` `// 重新计算角度``}``}``}``});``// 创建删除按钮``Button(``'删除'``).onClick(() => {``this``.cells.splice(index, 1);` `// 从单元格数组中删除当前单元格``this``.calculateAngles();` `// 重新计算角度``});``}.width(``'100%'``).justifyContent(FlexAlign.SpaceBetween)` `// 设置行的宽度和内容对齐方式``.padding({ left: 40, right: 40 });` `// 设置左右内边距``});``}.layoutWeight(1);` `// 设置滚动区域占据剩余空间``}.layoutWeight(1)` `// 设置滚动区域占据剩余空间``.margin({ top: 20, bottom: 20 })` `// 设置上下外边距``.align(Alignment.Top);` `// 设置对齐方式为顶部对齐` `// 创建添加新内容按钮``Button(``'添加新内容'``).onClick(() => {``// 向单元格数组中添加新单元格``this``.cells.push(``new` `Cell(1,` `"新内容"``,` `this``.colorPalette[``this``.colorIndex++ %` `this``.colorPalette.length]));``this``.calculateAngles();` `// 重新计算角度``}).margin({ top: 20, bottom: 20 });` `// 设置按钮的上下外边距``}``.height(``'100%'``)` `// 设置组件高度为100%``.width(``'100%'``)` `// 设置组件宽度为100%``.backgroundColor(``"#f5f8ff"``);` `// 设置组件背景颜色``}``}`

  

本博客参考FlowerCloud机场。转载请注明出处!

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

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

相关文章

开源 - Ideal库 - 常用时间转换扩展方法(二)Qv

合集 - Ideal库 - Common库(2)1.开源 - Ideal库 - 常用时间转换扩展方法(一)11-07:悠兔机场2.开源 - Ideal库 - 常用时间转换扩展方法(二)11-09收起 书接上回,我们继续来分享一些关于时间转换的常用扩展方法。01、时间转日期时间 TimeOnly 该方式是把TimeOnly类型转为Date…

存储器的知识

以W25Q256为例子(外部Flash),结构如下:扇区的内部结构以F10系列为例子 64位代表的是8个字节的数据,一个地址最多可以存储8个字节的数据(double数据类型就是8个字节),4k,16k,64k,32k代表的是地址的总数,主存储块又可以分为很多个页,页里面也有存储的空间大小嵌入式闪存的组成

什么是git,什么是github,git和github的使用

Git实战注意:本项目是学习笔记,来自于哔哩哔哩武沛齐老师的Git实战视频, 网址:【武沛齐老师讲git,看完绝对上瘾!!!】 https://www.bilibili.com/video/BV1ne4y1E7np/?share_source=copy_web&vd_source=2c9a5d5590d3759367594e264ff079c4另外,因为这个博客是我直接…

law Intermediate walkthrough pg

靶场很简单分数只有10分跟平常做的20分的中级靶场比确实简单 我拿来放松的 算下来30分钟解决战斗 nmap 扫到80端口web界面 是个框架 搜exp https://www.exploit-db.com/exploits/52023 他的脚本可能有点问题看不到回显 我们审脚本直接看到漏洞点所在 命令执行 curl -s -d "…

streamlit run执行报错,Invalid value: File does not exist: XXX.py

streamlit run执行报错,Invalid value: File does not exist: XXX.py 在终端执行 streamlit run xxx.py 的时候报错提示 Invalid value: File does not exist: XXX.py 网上众说纷纭,但是我个人的解决方法其实非常简单 在终端中执行的时候会发现中间多了个warning翻译过来就是…

03_muduo_base3

5.6 互斥锁和条件变量的封装 类图该类是封装了互斥锁的一些基本操作,包括互斥锁的初始化、销毁、上锁、解锁等功能。但是实际上使用RAII技术又封装了一个类,那就是MutexLockGuard。这主要也是采取了类似智能指针的封装思路,让互斥锁的生命周期交给操作系统去管理,释放的时机…

B样条(BSpline,即 Basis Spline)

B 样条(BSpline)是一种在计算机图形学、计算机辅助设计、数值分析等领域广泛应用的数学曲线和曲面表示方法。以下是对 B 样条的详细定义: 一、基本概念 B 样条是基于一系列控制点(Control Points)来定义曲线或曲面的。它通过一个特定的基函数(Basis Functions)集合与这些…

实验3 类和对象

实验任务1: button.hpp#pragma once#include <iostream> #include <string>using std::string; using std::cout;// 按钮类 class Button { public:Button(const string &text);string get_label() const;void click();private:string label; };Button::Butto…

【LaTex 2024软件下载与安装教程】

1、安装包 Latex2024: 链接:https://pan.quark.cn/s/1dad34ca4d8f 提取码:5bja 2、安装教程 1) 双击压缩包内intall-tl-windows.bat安装,弹窗安装对话框2) 自动弹出安装窗口,如果弹出以下窗口说明文件夹目录太长或者有中文,建议放磁盘根目录;如果没有弹出…

实现qt 窗口无边框拖拽

无边框拖拽是参考Qt实战6.万能的无边框窗口(FramelessWindow) - Qt小罗 - 博客园的文章,对其代码进行修改而来。 本篇一共会提供本人写的无边框的代码以及Qt实战6.万能的无边框窗口(FramelessWindow) - Qt小罗 - 博客园里面的完整代码供大家参考. 代码使用的话,我是直接让…

20222411 2024-2025-1 《网络与系统攻防技术》实验四实验报告

1.实验内容 1.1实践内容 一、恶意代码文件类型标识、脱壳与字符串提取 对提供的rada恶意代码样本,进行文件类型识别,脱壳与字符串提取,以获得rada恶意代码的编写作者,具体操作如下: (1)使用文件格式和类型识别工具,给出rada恶意代码样本的文件格式、运行平台和加壳工具…

第六次高级语言程序设计作业

这个作业属于哪个课程:https://edu.cnblogs.com/campus/fzu/2024C/ 这个作业要求在哪里: https://edu.cnblogs.com/campus/fzu/2024C/homework/13303 学号:102400110 姓名:阿卜杜拉阿布力克木 123456789101112这次作业难度还是很高我会继续努力!