【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%') } }