鸿蒙开发案例:七巧板

news/2024/11/7 21:27:34/文章来源:https://www.cnblogs.com/zhongcx/p/18534029

 

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

本文介绍的拖动七巧板游戏是一个简单的益智游戏,用户可以通过拖动和旋转不同形状的七巧板块来完成拼图任务。整个游戏使用鸿蒙Next框架开发,利用其强大的UI构建能力和数据响应机制,实现了流畅的用户体验。

【2】环境准备

电脑系统:windows 10

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

工程版本:API 12

真机:Mate 60 Pro

语言:ArkTS、ArkUI

【3】关键技术点

1. TangramBlock 类定义 游戏的核心在于TangramBlock类的定义,它封装了每个七巧板块的属性和行为。类中包含了宽度、高度、颜色、初始和当前偏移量、旋转角度等属性,并提供了重置数据的方法。这为后续的数据绑定和UI渲染奠定了基础。

2. 数据绑定与响应式更新 在鸿蒙Next中,使用@ObservedV2和@Trace装饰器可以轻松实现数据的观察和响应式更新。每当TangramBlock实例中的属性发生变化时,UI会自动更新以反映最新的状态。这种机制极大地简化了数据同步的工作,使得开发者可以专注于逻辑实现而无需担心UI更新的问题。

3. UI构建与布局管理 鸿蒙Next提供了丰富的UI组件和布局工具,使得构建复杂的用户界面变得简单。在这个项目中,我们使用了Column、Stack、Polygon等组件来构建七巧板块的布局。通过嵌套这些组件,我们可以灵活地控制每个板块的位置和大小。

4. 手势处理与交互 为了实现拖动和旋转功能,我们使用了PanGesture和rotate方法来处理用户的触摸和手势操作。当用户拖动板块时,通过更新initialOffsetX和initialOffsetY属性,可以实时反映板块的位置变化。同样,通过增加或减少rotationAngle属性,可以实现板块的旋转效果。

5. 动画与过渡 鸿蒙Next内置了丰富的动画和过渡效果,使得用户交互更加自然。在本项目中,我们使用了animateTo方法来平滑地更新板块的状态,从而提升了用户体验。

5.1 旋转动画属性

.rotate({angle: block.rotationAngle,
})

5.2 翻转动画属性

.rotate({x: 0,y: 1,z: 0,angle: block.flipAngle,centerX: block.width / 2, // 中心点X坐标centerY: block.height / 2, // 中心点Y坐标
})

5.3 平移动画属性

.translate({ x: block.initialOffsetX, y: block.initialOffsetY, z: 0 })

【完整代码】

@ObservedV2 // 监听数据变化的装饰器
class TangramBlock { // 定义七巧板类width: number; // 宽度height: number; // 高度points: Array<[number, number]>; // 点坐标数组color: string; // 颜色@Trace initialOffsetX: number; // 初始X偏移量@Trace initialOffsetY: number; // 初始Y偏移量@Trace currentOffsetX: number; // 当前X偏移量@Trace currentOffsetY: number; // 当前Y偏移量@Trace rotationAngle: number; // 旋转角度@Trace flipAngle: number = 0; // 翻转角度,默认为0@Trace rotateValue: number; // 旋转值defaultInitialOffsetX: number; // 默认初始X偏移量defaultInitialOffsetY: number; // 默认初始Y偏移量defaultRotationAngle: number; // 默认旋转角度constructor(color: string, width: number, height: number, initialOffsetX: number, initialOffsetY: number,rotationAngle: number, points: Array<[number, number]>) {this.initialOffsetX = this.currentOffsetX = this.defaultInitialOffsetX = initialOffsetX; // 初始化X偏移量this.initialOffsetY = this.currentOffsetY = this.defaultInitialOffsetY = initialOffsetY; // 初始化Y偏移量this.rotationAngle = this.rotateValue = this.defaultRotationAngle = rotationAngle; // 初始化旋转角度this.color = color; // 设置颜色this.width = width; // 设置宽度this.height = height; // 设置高度this.points = points; // 设置点坐标数组}resetData() { // 重置数据方法this.flipAngle = 0; // 重置翻转角度this.initialOffsetX = this.currentOffsetX = this.defaultInitialOffsetX; // 重置初始X偏移量this.initialOffsetY = this.currentOffsetY = this.defaultInitialOffsetY; // 重置初始Y偏移量this.rotationAngle = this.rotateValue = this.defaultRotationAngle; // 重置旋转角度}
}const baseUnitLength: number = 80; // 基本单位长度@Entry // 入口组件
@Component // 定义组件
export struct Index { // 主组件@State selectedBlockIndex: number = -1; // 当前选中位置@State blocks: TangramBlock[] = [// 七巧板数组// 小直角等腰三角形new TangramBlock("#fed8e5", baseUnitLength, baseUnitLength, -33.58, -58.02, 135,[[0, 0], [baseUnitLength, 0], [0, baseUnitLength]]),new TangramBlock("#0a0bef", baseUnitLength, baseUnitLength, 78.76, 54.15, 45,[[0, 0], [baseUnitLength, 0], [0, baseUnitLength]]),// 中直角等腰三角形new TangramBlock("#ff0d0c", baseUnitLength * Math.sqrt(2), baseUnitLength * Math.sqrt(2), -33.16, -1.43, -90,[[0, 0], [baseUnitLength * Math.sqrt(2), 0], [0, baseUnitLength * Math.sqrt(2)]]),// 大直角等腰三角形new TangramBlock("#ffa60a", baseUnitLength * 2, baseUnitLength * 2, 22.46, -172, -135,[[0, 0], [baseUnitLength * 2, 0], [0, baseUnitLength * 2]]),new TangramBlock("#3da56a", baseUnitLength * 2, baseUnitLength * 2, 135.65, -59.34, -45,[[0, 0], [baseUnitLength * 2, 0], [0, baseUnitLength * 2]]),// 正方形new TangramBlock("#ffff0b", baseUnitLength, baseUnitLength, 23.07, -1.84, -45,[[0, 0], [baseUnitLength, 0], [baseUnitLength, baseUnitLength], [0, baseUnitLength]]),// 平行四边形new TangramBlock("#5e0b9b", baseUnitLength * 2, baseUnitLength, -61.53, -85.97, 45,[[0, 0], [baseUnitLength, 0], [baseUnitLength * 2, baseUnitLength], [baseUnitLength, baseUnitLength]])];build() { // 构建方法Column({ space: 30 }) { // 创建垂直布局Stack() { // 创建堆叠布局ForEach(this.blocks, (block: TangramBlock, index: number) => { // 遍历七巧板数组Stack() { // 创建堆叠布局Polygon({ width: block.width, height: block.height })// 绘制多边形.points(block.points)// 设置多边形顶点坐标.fill(block.color)// 填充颜色.draggable(false)// 长按不可拖动.rotate({ angle: block.rotationAngle }) // 旋转角度}.rotate({// 旋转x: 0,y: 1,z: 0,angle: block.flipAngle,centerX: block.width / 2, // 中心点X坐标centerY: block.height / 2, // 中心点Y坐标}).width(block.width) // 设置宽度.height(block.height) // 设置高度.onTouch(() => { // 触摸事件this.selectedBlockIndex = index; // 设置选中索引}).draggable(false) // 长按不可拖动.translate({ x: block.initialOffsetX, y: block.initialOffsetY, z: 0 }) // 平移.gesture( // 手势操作PanGesture()// 拖动手势.onActionUpdate((event: GestureEvent | undefined) => { // 更新事件if (event) {block.initialOffsetX = block.currentOffsetX + event.offsetX; // 更新X偏移量block.initialOffsetY = block.currentOffsetY + event.offsetY; // 更新Y偏移量}}).onActionEnd((event: GestureEvent | undefined) => { // 结束事件if (event) {block.currentOffsetX = block.initialOffsetX; // 更新当前X偏移量block.currentOffsetY = block.initialOffsetY; // 更新当前Y偏移量}})).zIndex(this.selectedBlockIndex == index ? 1 : 0) // 设置层级.borderWidth(2) // 边框宽度.borderStyle(BorderStyle.Dashed) // 边框样式.borderColor(this.selectedBlockIndex == index ? "#80a8a8a8" : Color.Transparent) // 边框颜色})}.width('100%').height('750lpx') // 设置宽高.backgroundColor("#e4f2f5") // 背景颜色// 旋转角度计数器Column({ space: 5 }) { // 创建垂直布局,设置间距Text(`旋转角度(间隔5)`).fontColor(Color.Black) // 显示旋转角度文本,设置字体颜色Counter() { // 创建计数器组件Text(`${this.selectedBlockIndex != -1 ? this.blocks[this.selectedBlockIndex].rotationAngle :'-'}`)// 显示当前选中七巧板的旋转角度或占位符.fontColor(Color.Black) // 设置字体颜色}.width(300) // 设置计数器宽度.onInc(() => { // 增加按钮的点击事件if (this.selectedBlockIndex != -1) {animateTo({}, () => {this.blocks[this.selectedBlockIndex].rotationAngle += 5; // 增加旋转角度})}}).onDec(() => { // 减少按钮的点击事件if (this.selectedBlockIndex != -1) {animateTo({}, () => {this.blocks[this.selectedBlockIndex].rotationAngle -= 5; // 减少旋转角度})}});}// 旋转角度计数器Column({ space: 5 }) { // 创建垂直布局,设置间距Text(`旋转角度(间隔45)`).fontColor(Color.Black) // 显示旋转角度文本,设置字体颜色Counter() { // 创建计数器组件Text(`${this.selectedBlockIndex != -1 ? this.blocks[this.selectedBlockIndex].rotationAngle :'-'}`)// 显示当前选中七巧板的旋转角度或占位符.fontColor(Color.Black) // 设置字体颜色}.width(300) // 设置计数器宽度.onInc(() => { // 增加按钮的点击事件if (this.selectedBlockIndex != -1) {animateTo({}, () => {this.blocks[this.selectedBlockIndex].rotationAngle += 45; // 增加旋转角度})}}).onDec(() => { // 减少按钮的点击事件if (this.selectedBlockIndex != -1) {animateTo({}, () => {this.blocks[this.selectedBlockIndex].rotationAngle -= 45; // 减少旋转角度})}});}// 翻转按钮Row() { // 创建水平布局Button('向左翻转').onClick(() => { // 左翻转按钮点击事件animateTo({}, () => {if (this.selectedBlockIndex != -1) {this.blocks[this.selectedBlockIndex].flipAngle -= 180; // 减少翻转角度}});});Button('向右翻转').onClick(() => { // 右翻转按钮点击事件animateTo({}, () => {if (this.selectedBlockIndex != -1) {this.blocks[this.selectedBlockIndex].flipAngle += 180; // 增加翻转角度}});});}.width('100%').justifyContent(FlexAlign.SpaceEvenly) // 设置宽度和内容对齐方式// 重置和隐藏边框按钮Row() { // 创建水平布局Button('重置').onClick(() => { // 重置按钮点击事件animateTo({}, () => {for (let i = 0; i < this.blocks.length; i++) {this.blocks[i].resetData(); // 重置七巧板数据}this.selectedBlockIndex = -1; // 重置选中索引});});Button('隐藏边框').onClick(() => { // 隐藏边框按钮点击事件this.selectedBlockIndex = -1; // 重置选中索引});}.width('100%').justifyContent(FlexAlign.SpaceEvenly) // 设置宽度和内容对齐}.width('100%').height('100%')}
}

  

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

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

相关文章

windows MacBook 下载视频网站视频

下载yt-dlp github地址:https://github.com/yt-dlp/yt-dlp 下载:https://github.com/yt-dlp/yt-dlp/releases下载ffmpeg 官网:https://ffmpeg.org/解压后,只有bin下的这2个文件放C盘目录下 将下面这3个程序,挪到:C:\Windows\System32下载视频 在任意路径:cmd命令:yt-dl…

聊聊接口测试用例设计规范

1、通过性验证: 先按照接口文档传入所有必填字段并且字段值在正确范围内,预期返回正确结果 2、参数验证(正向/逆向):必填参数:针对每个必填参数,都设计一条参数为空的测试用例,接口错误信息返回正确 非必填参数:设计一条用例所有非必填的参数都传入值,非必填参数(类…

2287: 【例28.3】 数列分段

include <bits/stdc++.h> using namespace std; int n, m, sum, num; int main( ) { cin >> n >> m; for (int i=1;i<=n;i++) { int e; cin >> e; if (num+e>m) { sum++; num=e; } else { num+=e; } } cout << sum+1; return 0; } 反思:这…

CRM系统主要是干什么的?

什么是CRM 系统?CRM系统到底是干什么的?不同的企业人员该如何利用CRM去解决他们的问题等等,问题太多了,我想着就就专门出一期内容,来为大家详细介绍。 干货满满,建议收藏!! 首先第一个问题,什么是CRM系统? CRM是Customer Relationship Management 的缩写,就是客户关…

11月7日 NOIP模拟(难题math、矩阵游戏matrix、括号序列seq、道路road) - 模拟赛记录

Preface T1 试图找规律失败,正经推反而几分钟就出来了。以后应该少想这些歪门邪道(除非实在闲的蛋疼或者没有一点头绪,且必须要打完所有能打的子任务比如暴力或特殊性质;而且必须在用常规方法思考过后,才能够用一些稍微不那么常规的方法) 至于 T2、T3、T4,因为知道 T1 浪…

ESP32学习笔记2(GPIO的数字输入输出功能)

1. 普通5mm直径LED参数测定实验 以上为普通5mm直径LED,手册建议持续工作电流为20mA以内。以下,采用学生电源(带控压限流功能)通过限流电阻170欧给各色LED供电,通过缓慢加压测流和观察LED亮度的方法,确定电流、压降与亮度关系,实测该批次LED颜色与压降大致如下: 颜色 …

2024/11/7日工作总结

学习JS基础知识: 1.事件绑定:点击查看代码 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title> </head> <body><img id="龙泡泡" src="../imgs/年…

实验三 c++

实验任务一 源代码 button.hpp1 #pragma once2 3 #include<iostream>4 #include<string>5 6 using std::string;7 using std::cout;8 9 class Button{ 10 public: 11 Button(const string &text); 12 string get_label()const; 13 void click(); 1…

学习openeuler操作系统的记录本

1.下载以及配置openeuler在官网里面下载openeuler操作系统,在官网的文档里面里面查看相对应的注意事项,(一定要会阅读官方文档),在官网查看下载的对应操作系统需要的最小cpu,以及磁盘大小等分配合适的虚拟硬盘,配置的过程要一步一步来,防止出现分配不合理,而导致的操作…

AWVS安装及破解

以kali为例安装AWVS复制安装文件到kali中 AWVS百度网盘下载 root用户打开kali并把安装包解压到/opt/AWVS路径中 7z x acunetix_23.11.231123131_x64.7z -o/opt/AWVS/编辑host文件 vim /etc/hosts将以下内容加在hosts文件尾部 127.0.0.1 erp.acunetix.com127.0.0.1 erp.acunetix…

这款Chrome 插件,使浏览器页面快速滑动到最底部和最顶部,并且还能...

前言 前几日我在使用谷歌浏览器,也就是chrome的时候,浏览一个内容很长的页面,由于页面上的内容有前后关联,所以我必须不停地切换到上面和下面。这非常不方便。使我非常抓狂。后来,我灵机一动,去谷歌浏览器的插件市场上搜索了一下有没有快速回到底部和顶部的插件,结果,还…

数据结构_链表_单向循环链表 双向链表的初始化、插入、删除、修改、查询打印(基于C语言实现)

一、单向循环链表的原理与应用 思考:对于单向链表而言,想要遍历链表,则必须从链表的首结点开始进行遍历,请问有没有更简单的方案实现链表中的数据的增删改查? 回答:是有的,可以使用单向循环的链表进行设计,单向循环的链表的使用规则和普通的单向链表没有较大的区别,需…