鴻蒙開發案例:七巧板

zhongcx發表於2024-11-07

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

  

相關文章