📖 前言

最近在学习鸿蒙原生开发,想着从一个小Demo入手练练手。考虑到日常玩游戏时经常需要掷骰子,就决定开发一个简洁美观的掷骰子应用。本文将详细记录整个开发过程,包括项目创建、UI设计、状态管理、动画实现等核心内容,希望能帮到正在学习鸿蒙开发的小伙伴们!

先看最终效果:

在这里插入图片描述


一、项目概述

1.1 功能需求

这个掷骰子App虽然简单,但我希望它能具备以下功能:

  • ✅ 点击按钮掷骰子
  • ✅ 骰子点数随机变化(1-6点)
  • ✅ 掷骰子动画效果(快速切换显示)
  • ✅ 统计掷骰子次数
  • ✅ 重置计数功能
  • ✅ 简洁美观的UI设计

1.2 技术栈

技术 说明
开发框架 ArkUI(声明式UI)
开发语言 ArkTS(TypeScript扩展)
API版本 HarmonyOS Next
开发工具 DevEco Studio
项目模型 Stage模型

二、项目创建与环境配置

2.1 创建新项目

  1. 打开 DevEco Studio,选择 Create Project
  2. 选择 Empty Ability 模板(适用于纯应用开发)
  3. 填写项目信息:
    • Project name: DiceApp
    • Bundle name: com.example.diceapp
    • Save location: E:\HMproject\Project\DiceApp
    • Compile SDK: 选择最新的 HarmonyOS Next SDK
    • Model: Stage

点击 Finish 完成项目创建。

2.2 项目结构分析

创建完成后,项目目录结构如下:

DiceApp/
├── AppScope/                 # 应用全局配置
│   ├── app.json5            # 应用配置(包名、版本、图标等)
│   └── resources/           # 全局资源
├── entry/                    # 主模块
│   ├── src/main/
│   │   ├── ets/             # ArkTS源码
│   │   │   ├── entryability/
│   │   │   │   └── EntryAbility.ets    # 应用入口
│   │   │   └── pages/
│   │   │       └── Index.ets           # 主页面
│   │   └── resources/       # 资源文件
│   │       ├── base/element/   # 字符串、颜色等
│   │       ├── base/media/     # 图片资源
│   │       └── base/profile/   # 配置文件
│   ├── build-profile.json5  # 构建配置
│   └── module.json5         # 模块配置
├── oh_modules/              # 依赖包
└── build-profile.json5      # 项目构建配置

2.3 核心配置文件解读

app.json5 - 应用全局配置
{
  "app": {
    "bundleName": "com.example.diceapp",    // 应用包名,唯一标识
    "vendor": "example",                     // 开发者名称
    "versionCode": 1000000,                  // 版本号(数值)
    "versionName": "1.0.0",                  // 版本名(字符串)
    "icon": "$media:layered_image",          // 应用图标
    "label": "$string:app_name"              // 应用名称
  }
}
module.json5 - 模块配置

这个文件定义了应用的基本能力(Ability)和页面路由:

{
  "module": {
    "name": "entry",
    "type": "entry",
    "deviceTypes": ["phone"],               // 支持设备类型
    "pages": "$profile:main_pages",         // 页面路由配置
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "exported": true,
        "skills": [
          {
            "entities": ["entity.system.home"],
            "actions": ["ohos.want.action.home"]
          }
        ]
      }
    ]
  }
}

三、UI设计与实现

3.1 设计思路

骰子App的UI设计我采用了以下风格:

  • 暗色主题: 黑色背景,红色骰子,营造高级感
  • 卡片式设计: 骰子作为主体,带有阴影效果
  • 状态反馈: 按钮颜色变化、文字提示等

3.2 骰子点数实现原理

骰子是一个3x3的点阵,每个位置可能有点或无点。我用一个二维数组来表示6种点数:

骰子点阵布局:
[0] [1] [2]
[3] [4] [5]
[6] [7] [8]

对于每个点数(1-6),定义9个位置的显示状态:

private readonly DICE_DOTS: boolean[][] = [
  // 1点:只有中心
  [false, false, false, false, true,  false, false, false, false],
  // 2点:左上和右下
  [false, false, true,  false, false, false, true,  false, false],
  // 3点:对角线
  [false, false, true,  false, true,  false, true,  false, false],
  // 4点:四角
  [true,  false, true,  false, false, false, true,  false, true],
  // 5点:四角+中心
  [true,  false, true,  false, true,  false, true,  false, true],
  // 6点:左右两列
  [true,  false, true,  true,  false, true,  true,  false, true]
]

3.3 页面布局实现

主页面使用 ColumnRow 进行布局,整体结构如下:

@Entry
@Component
struct Index {
  // 状态变量
  @State currentValue: number = 1      // 当前点数
  @State isRolling: boolean = false    // 是否正在掷骰子
  @State rollCount: number = 0         // 掷骰子次数

  build() {
    Scroll() {
      Column() {
        // 1. 顶部标题栏
        Row() {
          Text('🎲 掷骰子')
          Blank()
          Text('共掷 ' + this.rollCount + ' 次')
        }

        // 2. 骰子展示区域
        Column() {
          // 3x3圆点矩阵
        }

        // 3. 点数显示
        Text('点数: ' + this.currentValue)

        // 4. 操作按钮
        Button('🎲 掷骰子')
        Button('🔄 重置计数')
      }
    }
  }
}

3.4 骰子点阵UI实现

使用9个 Circle 组件组成骰子的点阵:

private dotColor(i: number): ResourceColor {
  return this.DICE_DOTS[this.currentValue - 1][i] ? Color.White : Color.Transparent
}

Column() {
  Row() {
    Circle().width(26).height(26).fill(this.dotColor(0))
    Circle().width(26).height(26).fill(this.dotColor(1))
    Circle().width(26).height(26).fill(this.dotColor(2))
  }
  Row() {
    Circle().width(26).height(26).fill(this.dotColor(3))
    Circle().width(26).height(26).fill(this.dotColor(4))
    Circle().width(26).height(26).fill(this.dotColor(5))
  }
  Row() {
    Circle().width(26).height(26).fill(this.dotColor(6))
    Circle().width(26).height(26).fill(this.dotColor(7))
    Circle().width(26).height(26).fill(this.dotColor(8))
  }
}
.width(180).height(180)
.backgroundColor('#FF3B30')          // 红色背景
.borderRadius(24)                   // 圆角
.shadow({                           // 阴影效果
  radius: 20,
  color: '#40FF3B30',
  offsetX: 0,
  offsetY: 8
})

效果说明:

  • 骰子主体为红色背景(#FF3B30)
  • 圆角设计(24vp)更现代
  • 添加阴影增加立体感

四、状态管理与动画实现

4.1 状态变量详解

ArkUI 使用 @State 装饰器声明状态变量,当状态变化时UI会自动更新:

@State currentValue: number = 1      // 当前点数
@State isRolling: boolean = false    // 掷骰子状态
@State rollCount: number = 0         // 掷骰子总次数

4.2 掷骰子动画实现

这是本App的核心功能!通过 setInterval 实现快速切换点数的动画效果:

private rollIntervalId: number = -1

private rollDice(): void {
  if (this.isRolling) return    // 防止重复点击
  
  this.isRolling = true
  let count = 0
  
  this.rollIntervalId = setInterval(() => {
    count++
    this.currentValue = Math.floor(Math.random() * 6) + 1  // 随机1-6
    
    if (count >= 8) {
      clearInterval(this.rollIntervalId)     // 停止动画
      this.rollIntervalId = -1
      this.isRolling = false
      this.rollCount++                       // 累计次数
    }
  }, 100)  // 每100ms切换一次
}

动画参数解析:

  • 切换间隔:100ms(快速闪烁效果)
  • 切换次数:8次(总时长800ms)
  • 随机算法:Math.random() * 6 + 1 生成1-6的随机数

4.3 生命周期管理

很重要!在组件销毁时清除定时器,避免内存泄漏:

aboutToDisappear(): void {
  if (this.rollIntervalId !== -1) {
    clearInterval(this.rollIntervalId)
  }
}

4.4 重置功能

private clearCount(): void {
  this.rollCount = 0
  this.currentValue = 1
}

五、完整代码实现

5.1 主页面完整代码(Index.ets)

@Entry
@Component
struct Index {
  // 骰子点数映射表
  private readonly DICE_DOTS: boolean[][] = [
    [false, false, false, false, true,  false, false, false, false],  // 1
    [false, false, true,  false, false, false, true,  false, false],  // 2
    [false, false, true,  false, true,  false, true,  false, false],  // 3
    [true,  false, true,  false, false, false, true,  false, true],   // 4
    [true,  false, true,  false, true,  false, true,  false, true],   // 5
    [true,  false, true,  true,  false, true,  true,  false, true]    // 6
  ]

  // 状态变量
  @State currentValue: number = 1
  @State isRolling: boolean = false
  @State rollCount: number = 0

  private rollIntervalId: number = -1

  // 生命周期:组件销毁时清理
  aboutToDisappear(): void {
    if (this.rollIntervalId !== -1) {
      clearInterval(this.rollIntervalId)
    }
  }

  // 掷骰子方法
  private rollDice(): void {
    if (this.isRolling) return
    this.isRolling = true
    let count = 0
    this.rollIntervalId = setInterval(() => {
      count++
      this.currentValue = Math.floor(Math.random() * 6) + 1
      if (count >= 8) {
        clearInterval(this.rollIntervalId)
        this.rollIntervalId = -1
        this.isRolling = false
        this.rollCount++
      }
    }, 100)
  }

  // 重置计数
  private clearCount(): void {
    this.rollCount = 0
    this.currentValue = 1
  }

  // 获取点的颜色
  private dotColor(i: number): ResourceColor {
    return this.DICE_DOTS[this.currentValue - 1][i] ? Color.White : Color.Transparent
  }

  build() {
    Scroll() {
      Column() {
        // 顶部标题栏
        Row() {
          Text('🎲 掷骰子')
            .fontSize(28)
            .fontWeight(FontWeight.Bold)
            .fontColor(Color.White)
          Blank()
          Text('共掷 ' + this.rollCount + ' 次')
            .fontSize(14)
            .fontColor('#8E8E93')
        }
        .width('100%')
        .padding(20)

        // 骰子展示
        Column() {
          Row() {
            Circle().width(26).height(26).fill(this.dotColor(0)).margin({ left: 6, right: 6 })
            Circle().width(26).height(26).fill(this.dotColor(1)).margin({ left: 6, right: 6 })
            Circle().width(26).height(26).fill(this.dotColor(2)).margin({ left: 6, right: 6 })
          }.width('100%').justifyContent(FlexAlign.Center)

          Row() {
            Circle().width(26).height(26).fill(this.dotColor(3)).margin({ left: 6, right: 6 })
            Circle().width(26).height(26).fill(this.dotColor(4)).margin({ left: 6, right: 6 })
            Circle().width(26).height(26).fill(this.dotColor(5)).margin({ left: 6, right: 6 })
          }.width('100%').justifyContent(FlexAlign.Center).margin({ top: 6 })

          Row() {
            Circle().width(26).height(26).fill(this.dotColor(6)).margin({ left: 6, right: 6 })
            Circle().width(26).height(26).fill(this.dotColor(7)).margin({ left: 6, right: 6 })
            Circle().width(26).height(26).fill(this.dotColor(8)).margin({ left: 6, right: 6 })
          }.width('100%').justifyContent(FlexAlign.Center).margin({ top: 6 })
        }
        .width(180)
        .height(180)
        .backgroundColor('#FF3B30')
        .borderRadius(24)
        .justifyContent(FlexAlign.Center)
        .shadow({ radius: 20, color: '#40FF3B30', offsetX: 0, offsetY: 8 })
        .margin({ top: 24 })

        // 点数显示
        Text('点数: ' + this.currentValue)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor('#8E8E93')
          .margin({ top: 12 })

        // 掷骰子按钮
        Button(this.isRolling ? '🎲 掷动中...' : '🎲 掷骰子')
          .fontSize(22)
          .fontWeight(FontWeight.Bold)
          .width(220)
          .height(60)
          .borderRadius(30)
          .backgroundColor(this.isRolling ? '#555555' : '#FF9F0A')
          .fontColor(Color.White)
          .enabled(!this.isRolling)
          .margin({ top: 20 })
          .onClick(() => { this.rollDice() })

        // 重置按钮(条件显示)
        if (this.rollCount > 0) {
          Button('🔄 重置计数')
            .fontSize(16)
            .height(44)
            .borderRadius(22)
            .backgroundColor('#333333')
            .fontColor('#8E8E93')
            .margin({ top: 16 })
            .onClick(() => { this.clearCount() })
        }

        // 提示文字(初始状态)
        if (this.rollCount === 0) {
          Text('点击"掷骰子"开始')
            .fontSize(16)
            .fontColor('#8E8E93')
            .margin({ top: 40 })
        }
      }
      .width('100%')
      .backgroundColor('#000000')
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#000000')
  }
}

5.2 入口Ability(EntryAbility.ets)

这是应用的生命周期管理类,负责加载主页面:

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'DiceApp', 'Ability onCreate');
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(0x0000, 'DiceApp', 'Failed to load content');
        return;
      }
      hilog.info(0x0000, 'DiceApp', 'Succeeded in loading content');
    });
  }

  // 其他生命周期方法...
}

六、运行与调试

6.1 构建项目

在 DevEco Studio 中:

  1. 点击菜单 Build > Build Hap(s)/APP(s) > Build Hap(s)
  2. 或使用快捷键构建项目

6.2 运行应用

方式一:模拟器运行
  1. 点击 Tools > Device Manager
  2. 创建或启动一个 Phone 模拟器
  3. 点击运行按钮(绿色三角形)
方式二:真机运行
  1. 连接华为手机(开启开发者模式)
  2. 在设备列表中选择真机
  3. 点击运行

6.3 运行效果展示

运行成功后,可以看到:

在这里插入图片描述

点击"掷骰子"按钮:

在这里插入图片描述


七、核心知识点总结

7.1 ArkUI 声明式UI

@Entry                    // 页面入口装饰器
@Component               // 组件装饰器
@State                   // 状态变量装饰器

7.2 常用组件

组件 用途
Column 垂直布局容器
Row 水平布局容器
Text 文本显示
Button 按钮
Circle 圆形(用于骰子点)
Scroll 可滚动容器

7.3 状态驱动UI

ArkUI 采用状态驱动的方式,当 @State 变量改变时,UI会自动刷新:

@State currentValue: number = 1

// 改变状态
this.currentValue = 5  // UI自动更新为5点

7.4 条件渲染

使用 if 语句控制组件的显示/隐藏:

if (this.rollCount > 0) {
  Button('🔄 重置计数')
}

八、扩展思路

这个Demo还可以继续扩展,比如:

  1. 多种骰子类型 - 添加D20、D12等不同面数的骰子
  2. 多骰子模式 - 同时掷多个骰子
  3. 历史记录 - 记录每次的点数
  4. 音效 - 添加掷骰子的音效
  5. 震动反馈 - 利用手机振动增强体验
  6. 自定义主题 - 允许用户切换骰子颜色、背景等

九、常见问题

Q1: setInterval 为什么不停止?

确保在 aboutToDisappear 生命周期中清理定时器,否则可能导致内存泄漏。

Q2: 按钮连续点击会怎样?

代码中通过 if (this.isRolling) return 防止重复触发,同时按钮设置 enabled(!this.isRolling) 禁用状态。

Q3: 如何修改骰子颜色?

修改 backgroundColor('#FF3B30') 中的颜色值即可。


十、总结

通过这个掷骰子App的开发,我学习了:

✅ HarmonyOS项目结构和配置
✅ ArkUI声明式UI开发模式
✅ 状态管理(@State装饰器)
✅ 定时器实现动画效果
✅ 条件渲染和动态UI
✅ 组件生命周期管理

虽然功能简单,但涵盖了鸿蒙开发的核心概念。希望这篇博文能帮助到正在学习鸿蒙开发的小伙伴们!


参考资料


如果这篇文章对你有帮助,欢迎点赞、收藏、评论!你的支持是我创作的动力! 🎲

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐