鸿蒙开发案例:七巧板

【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%' ) } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了