鸿蒙开发案例:七巧板

 

【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 旋转动画属性

1
2
3
.rotate({
  angle: block.rotationAngle,
})

5.2 翻转动画属性

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

5.3 平移动画属性

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

【完整代码】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
@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%')
  }
}

  

posted @   zhongcx  阅读(398)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示