鸿蒙 ArkUI 实现敲木鱼小游戏

敲木鱼是一款具有禅意的趣味小游戏,本文将通过鸿蒙 ArkUI 框架的实现代码,逐步解析其核心技术点,包括动画驱动状态管理音效震动反馈等。

一、架构设计与工程搭建

1.1 项目结构解析

完整项目包含以下核心模块:

├── entry/src/main/ets/
│   ├── components/         // 自定义组件库
│   ├── model/              // 数据模型(如StateArray)
│   ├── pages/              // 页面组件(WoodenFishGame.ets)
│   └── resources/          // 多媒体资源(木鱼图标、音效)

通过模块化设计分离 UI层(pages)、逻辑层(model)、资源层(resources),符合鸿蒙应用开发规范。

1.2 组件化开发模式

使用 @Component 装饰器创建独立可复用的 UI 单元,@Entry 标记为页面入口。关键状态通过 @State 管理:

@Entry
@Component
struct WoodenFishGame {
  @State count: number = 0;                // 功德计数器
  @State scaleWood: number = 1;           // 木鱼缩放系数
  @State rotateWood: number = 0;          // 木鱼旋转角度
  @State animateTexts: Array<StateArray> = []; // 动画队列
  private audioPlayer?: media.AVPlayer;    // 音频播放器实例
  private autoPlay: boolean = false;       // 自动敲击标志位
}

@State 实现了 响应式编程:当变量值变化时,ArkUI 自动触发关联 UI 的重新渲染。

二、动画系统深度解析

2.1 木鱼敲击复合动画

动画分为 按压收缩(100ms)和 弹性恢复(200ms)两个阶段,通过 animateTo 实现平滑过渡:

playAnimation() {
  // 第一阶段:快速收缩+左旋
  animateTo({
    duration: 100, 
    curve: Curve.Friction // 摩擦曲线模拟物理阻力
  }, () => {
    this.scaleWood = 0.9; // X/Y轴缩放到90%
    this.rotateWood = -2; // 逆时针旋转2度
  });

  // 第二阶段:弹性恢复
  setTimeout(() => {
    animateTo({
      duration: 200,
      curve: Curve.Linear // 线性恢复保证流畅性
    }, () => {
      this.scaleWood = 1; 
      this.rotateWood = 0;
    });
  }, 100); // 延迟100ms衔接动画
}

曲线选择

  • Curve.Friction 模拟木槌敲击时的瞬间阻力
  • Curve.Linear 确保恢复过程无加速度干扰

2.2 功德文字飘浮动画

采用 动态数组管理 + 唯一ID标识 实现多实例独立控制:

countAnimation() {
  const animId = new Date().getTime(); // 时间戳生成唯一ID
  // 添加新动画元素
  this.animateTexts = [...this.animateTexts, { 
    id: animId, 
    opacity: 1, 
    offsetY: 20 
  }];
  
  // 启动渐隐上移动画
  animateTo({
    duration: 800,
    curve: Curve.EaseOut // 缓出效果模拟惯性
  }, () => {
    this.animateTexts = this.animateTexts.map(item => 
      item.id === animId ? 
        { ...item, opacity: 0, offsetY: -100 } : item
    );
  });

  // 动画完成后清理内存
  setTimeout(() => {
    this.animateTexts = this.animateTexts.filter(t => t.id !== animId);
  }, 800); // 与动画时长严格同步
}

关键技术点

  1. 数据驱动:通过修改 animateTexts 数组触发 ForEach 重新渲染
  2. 分层动画opacity 控制透明度,offsetY 控制垂直位移
  3. 内存优化:定时清理已完成动画元素,防止数组膨胀

三、多模态交互实现

3.1 触觉震动反馈

调用 @kit.SensorServiceKit 的振动模块实现触觉反馈:

vibrator.startVibration({
  type: "time",       // 按时间模式振动
  duration: 50        // 50ms短震动
}, {
  id: 0,              // 振动器ID
  usage: 'alarm'      // 资源使用场景标识
});

参数调优建议

  • 时长:50ms 短震动模拟木鱼敲击的“清脆感”
  • 强度:鸿蒙系统自动根据 usage 分配最佳强度等级

3.2 音频播放与资源管理

通过 media.AVPlayer 实现音效播放:

aboutToAppear(): void {
  media.createAVPlayer().then(player => {
    this.audioPlayer = player;
    this.audioPlayer.url = ""; 
    this.audioPlayer.loop = false; // 禁用循环播放
  });
}

// 敲击时重置播放进度
if (this.audioPlayer) {
  this.audioPlayer.seek(0);    // 定位到0毫秒
  this.audioPlayer.play();     // 播放音效
}

最佳实践

  1. 预加载资源:在 aboutToAppear 阶段提前初始化播放器
  2. 避免延迟:调用 seek(0) 确保每次点击即时发声
  3. 资源释放:需在 onPageHide 中调用 release() 防止内存泄漏

四、自动敲击功能实现

4.1 定时器与状态联动

通过 Toggle 组件切换自动敲击模式:

// 状态切换回调
Toggle({ type: ToggleType.Checkbox, isOn: false })
  .onChange((isOn: boolean) => {
    this.autoPlay = isOn;
    if (isOn) {
      this.startAutoPlay();
    } else {
      clearInterval(this.intervalId); // 清除指定定时器
    }
  });

// 启动定时器
private intervalId: number = 0;
startAutoPlay() {
  this.intervalId = setInterval(() => {
    if (this.autoPlay) this.handleTap();
  }, 400); // 400ms间隔模拟人类点击频率
}

关键改进点

  • 使用 intervalId 保存定时器引用,避免 clearInterval() 失效
  • 间隔时间 400ms 平衡流畅度与性能消耗

4.2 线程安全与性能保障

风险点:频繁的定时器触发可能导致 UI 线程阻塞
解决方案

// 在 aboutToDisappear 中清除定时器
aboutToDisappear() {
  clearInterval(this.intervalId);
}

确保页面隐藏时释放资源,避免后台线程持续运行。

五、UI 布局与渲染优化

5.1 层叠布局与动画合成

使用 Stack 实现多层 UI 元素的叠加渲染:

Stack() {
  // 木鱼主体(底层)
  Image($r("app.media.icon_wooden_fish"))
    .width(280)
    .height(280)
    .margin({ top: -10 })
    .scale({ x: this.scaleWood, y: this.scaleWood })
    .rotate({ angle: this.rotateWood });

  // 功德文字(上层)
  ForEach(this.animateTexts, (item, index) => {
    Text(`+1`)
      .translate({ y: -item.offsetY * index }) // 按索引错位显示
  });
}

渲染优化技巧

  • 为静态图片资源添加 fixedSize(true) 避免重复计算
  • 使用 translate 代替 margin 实现位移,减少布局重排

5.2 状态到 UI 的高效绑定

通过 链式调用 实现样式动态绑定:

Text(`功德 +${this.count}`)
  .fontSize(20)
  .fontColor('#4A4A4A')
  .margin({ 
    top: 20 + AppUtil.getStatusBarHeight() // 动态适配刘海屏
  })

适配方案

  • AppUtil.getStatusBarHeight() 获取状态栏高度,避免顶部遮挡
  • 使用鸿蒙的 弹性布局(Flex)自动适应不同屏幕尺寸

六、完整代码

import { media } from '@kit.MediaKit';
import { vibrator } from '@kit.SensorServiceKit';
import { AppUtil, ToastUtil } from '@pura/harmony-utils';
import { StateArray } from '../model/HomeModel';

@Entry
@Component
struct WoodenFishGame {
  @State count: number = 0;
  @State scaleWood: number = 1;
  @State rotateWood: number = 0;
  audioPlayer?: media.AVPlayer;
  // 添加自动敲击功能
  autoPlay: boolean = false;

  // 新增状态变量
  @State animateTexts: Array<StateArray> = []

  aboutToAppear(): void {
    media.createAVPlayer().then(player => {
      this.audioPlayer = player
      this.audioPlayer.url = ""
    })
  }

  startAutoPlay() {
    setInterval(() => {
      if (this.autoPlay) {
        this.handleTap();
      }
    }, 400);
  }

  // 敲击动画
  playAnimation() {
    animateTo({
      duration: 100,
      curve: Curve.Friction
    }, () => {
      this.scaleWood = 0.9;
      this.rotateWood = -2;
    });

    setTimeout(() => {
      animateTo({
        duration: 200,
        curve: Curve.Linear
      }, () => {
        this.scaleWood = 1;
        this.rotateWood = 0;
      });
    }, 100);
  }

  // 敲击处理
  handleTap() {
    this.count++;
    this.playAnimation();
    this.countAnimation();
    // 在handleTap中添加:
    vibrator.startVibration({
      type: "time",
      duration: 50
    }, {
      id: 0,
      usage: 'alarm'
    });

    // 播放音效
    if (this.audioPlayer) {
      this.audioPlayer.seek(0);
      this.audioPlayer.play();
    }
  }

  countAnimation(){
    // 生成唯一ID防止动画冲突
    const animId = new Date().getTime()
    // 初始化动画状态
    this.animateTexts = [...this.animateTexts, {id: animId, opacity: 1, offsetY: 20}]
    // 执行动画
    animateTo({
      duration: 800,
      curve: Curve.EaseOut
    }, () => {
      this.animateTexts = this.animateTexts.map(item => {

        if (item.id === animId) {
          return { id:item.id, opacity: 0, offsetY: -100 }
        }
        return item
      })
    })

    // 动画完成后清理
    setTimeout(() => {
      this.animateTexts = this.animateTexts.filter(t => t.id !== animId)
    }, 800)
  }

  build() {
    Column() {
      // 计数显示
      Text(`功德 +${this.count}`)
        .fontSize(20)
        .margin({ top: 20+AppUtil.getStatusBarHeight() })

      // 木鱼主体
      Stack() {

        // 可敲击部位
        Image($r("app.media.icon_wooden_fish"))
          .width(280)
          .height(280)
          .margin({ top: -10 })
          .scale({ x: this.scaleWood, y: this.scaleWood })
          .rotate({ angle: this.rotateWood })
          .onClick(() => this.handleTap())

        // 功德文字动画容器
        ForEach(this.animateTexts, (item:StateArray,index) => {
          Text(`+1`)
            .fontSize(24)
            .fontColor('#FFD700')
            .opacity(item.opacity)
            .margin({ top: -100}) // 初始位置调整
            .translate({ y: -item.offsetY*index }) // 使用translateY实现位移
            .animation({ duration: 800, curve: Curve.EaseOut })
        })
      }
      .margin({ top: 50 })


      Row(){
        // 自动敲击开关(扩展功能)
        Toggle({ type: ToggleType.Checkbox, isOn: false })
          .onChange((isOn: boolean) => {
            // 可扩展自动敲击功能
            this.autoPlay = isOn;
            if (isOn) {
              this.startAutoPlay();
            } else {
              clearInterval();
            }
          })
        Text("自动敲击")
      }.alignItems(VerticalAlign.Center)
      .justifyContent(FlexAlign.Center)
      .width("100%")
      .position({bottom:100})

    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f0f0f0')
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/979088.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ShenNiusModularity项目源码学习(14:ShenNius.Infrastructure项目分析)

ShenNius.Infrastructure项目用于定义ShenNius.Admin.Mvc项目和ShenNius.Admin.API项目共用的特性类、数据操作接口实现类、上下文类、通讯类&#xff0c;主要文件的用途如下&#xff1a;   Attributes文件夹保存特性类或过滤器类定义&#xff0c;主要包括&#xff1a;   …

Mysql表字段字符集未设置导致乱码问题

项目场景&#xff1a; 在使用mysql的text类型作为字段类型【未设置编码】&#xff0c;且表结构【设置了编码集】的条件下&#xff0c;查询表这个字段会出现乱码的情况。 问题描述 今日测试小伙伴给题主提出了一个bug&#xff0c;数据库当中的text文本字段在存储json的情况下&…

staruml绘制时序图和用例图

文章目录 1.文章介绍2.绘制用例图3.绘制时序图 1.文章介绍 之前&#xff0c;我们初步介绍了这个staruml软件的安装和如何使用这个软件对于uml类图进行绘制&#xff0c;当时我们是绘制了这个user类&#xff0c;实现了相关的接口&#xff0c;表示他们之间的关系&#xff0c;在今…

火狐浏览器多开指南:独立窗口独立IP教程

无论是跨境电商从业者需要管理多个店铺账号&#xff0c;还是海外社交媒体营销人员要运营多个社交平台账号&#xff0c;亦或是从事多账号广告投放的人员&#xff0c;都面临着一个共同的挑战 —— 如何高效管理多个账号&#xff0c;并确保每个账号的独立性。 在这种情况下&#…

DeepSeek赋能大模型内容安全,网易易盾AIGC内容风控解决方案三大升级

在近两年由AI引发的生产力革命的背后&#xff0c;一场关乎数字世界秩序的攻防战正在上演&#xff1a;AI生成的深度伪造视频导致企业品牌声誉损失日均超千万&#xff0c;批量生成的侵权内容使版权纠纷量与日俱增&#xff0c;黑灰产利用AI技术持续发起欺诈攻击。 与此同时&#…

【论文精读】YOLO-World:实时开放词汇目标检测

论文地址&#xff1a; YOLO-World: Real-Time Open-Vocabulary Object Detection 源代码&#xff1a;YOLO-World 摘要 YOLO系列检测器因其高效性和实用性而被广泛认可。然而&#xff0c;它们依赖于预定义和训练过的物体类别&#xff0c;这限制了其在开放场景中的适用性。为了…

开放标准(RFC 7519):JSON Web Token (JWT)

开放标准&#xff1a;JSON Web Token 前言基本使用整合Shiro登录自定义JWT认证过滤器配置Config自定义凭证匹配规则接口验证权限控制禁用session缓存的使用登录退出单用户登录Token刷新双Token方案单Token方案 前言 JSON Web Token &#xff08;JWT&#xff09; 是一种开放标准…

mysql架构查询执行流程(图解+描述)

目录 mysql架构查询执行流程 图解 描述 mysql架构查询执行流程 图解 描述 用户连接到数据库后&#xff0c;由连接器处理 连接器负责跟客户端建立连接、获取权限、维持和管理连接 客户端发送一条查询给服务器 服务器先检查查询缓存&#xff0c;如果命中缓存&#xff0c;则立…

k8s使用containerd作为容器运行时配置Harbor私有仓库与阿里云私有仓库以及镜像加速器,k8s基于containerd如何配置harbor私有仓库

至于containerd大家还需要在去学习以下使用的命令。 版本介绍 k8s&#xff1a;v1.28.2containerd&#xff1a;1.6.33 1.配置containerd镜像加速器 [rootmaster ~]# vim /etc/containerd/config.toml ---编辑containerd配置文件找到以下位置新添加 [plugins."io.contain…

【MySql】EXPLAIN执行计划全解析:15个字段深度解读与调优指南

文章目录 一、执行计划核心字段总览二、关键字段深度拆解1. type&#xff08;访问类型&#xff09;——查询性能的晴雨表典型场景分析&#xff1a; 2. key_len&#xff08;索引使用长度&#xff09;——索引利用率的检测仪计算示例&#xff1a; 3. Extra&#xff08;附加信息&a…

python-leetcode-最长有效括号

32. 最长有效括号 - 力扣&#xff08;LeetCode&#xff09; class Solution:def longestValidParentheses(self, s: str) -> int:stack [-1] # 存储索引&#xff0c;初始值 -1 代表“未匹配起点”max_length 0for i, char in enumerate(s):if char (:stack.append(i)els…

单目摄像头物体深度计算基础原理

三维空间物体表面点位与其在图像中对应点之间的相互关系&#xff0c;必须建立相机成像的几何模型&#xff0c;这些几何模型参数就是相机参数&#xff0c;而相机参数的求解就是相机标定。 相机的参数矩阵包括内参和外参&#xff1a; 外参&#xff1a;决定现实坐标到摄像机坐标。…

CF 106A.Card Game(Java实现)

问题分析 定义一个字符是王牌&#xff0c;打出第一张牌a&#xff0c;第二张牌b。如果只有a是王牌花色直接赢。如果a&#xff0c;b同花色且a>b则a赢&#xff1b;如果只有b是王牌&#xff0c;a输。如果a&#xff0c;b都不是王牌且不同花色&#xff0c;不比较直接输。 思路分析…

在 Vue 组件中,如何确认父组件在 add 模式下传入 value 的情况及其对子组件 getProducts() 方法的触发影响?

文章目录 父组件中 <ave-form> 的使用add 模式下触发逻辑value 的传入情况是否触发 getProducts()&#xff1f; 验证 add 模式下 getProducts() 是否触发结论&#xff1a; 检查父组件传入 value 的完整情况如何明确知道父组件传入的 value最终回答 父组件 index.vue子组件…

Python的那些事第三十四篇:基于 Plotly 的交互式图表与仪表板设计与应用

基于 Plotly 的交互式图表与仪表板设计与应用 摘要: 本文深入探讨了 Plotly 这一强大的交互式图表和仪表板库。首先介绍了 Plotly 的背景与发展历程,随后详细阐述了其核心功能特性,包括丰富的图表类型、高度的自定义能力以及便捷的交互操作。通过实际案例分析和示例代码展示…

瑞芯微RK安卓Android主板GPIO按键配置方法,触觉智能嵌入式开发

触觉智能分享&#xff0c;瑞芯微RK安卓Android主板GPIO按键配置方法&#xff0c;方便大家更好利用空闲IO&#xff01;由触觉智能Purple Pi OH鸿蒙开发板演示&#xff0c;搭载了瑞芯微RK3566四核处理器&#xff0c;树莓派卡片电脑设计&#xff0c;支持安卓Android、开源鸿蒙Open…

树莓百度百科更新!宜宾园区业务再添新篇

树莓集团宜宾园区业务不断拓展&#xff0c;主要体现在以下几个方面&#xff1a; 产业布局 -聚焦数字经济核心领域&#xff1a;涵盖软件开发、人工智能、大数据等&#xff0c;吸引众多上下游企业入驻&#xff0c;形成从芯片研发、软件开发到系统集成的完整产业链条。 -推进“双…

Orange 开源项目 - 集成阿里云大模型

1 阿里云的大模型服务平台百炼 阿里云的大模型服务平台百炼是一站式的大模型开发及应用构建平台。不论是开发者还是业务人员&#xff0c;都能深入参与大模型应用的设计和构建。您可以通过简单的界面操作&#xff0c;在5分钟内开发出一款大模型应用&#xff0c;或在几小时内训练…

rust 前端npm依赖工具rsup升级日志

rsup是使用 rust 编写的一个前端 npm 依赖包管理工具&#xff0c;可以获取到项目中依赖包的最新版本信息&#xff0c;并通过 web 服务的形式提供查看、升级操作等一一系列操作。 在前一篇文章中&#xff0c;记录初始的功能设计&#xff0c;自己的想法实现过程。在自己的使用过…

如何使用Docker一键本地化部署LibrePhotos搭建私有云相册

文章目录 前言1.关于LibrePhotos2.本地部署LibrePhotos3.LibrePhotos简单使用4. 安装内网穿透5.配置LibrePhotos公网地址6. 配置固定公网地址 前言 你是不是也经常对着手机里那一堆珍贵的照片发愁&#xff0c;心里想着&#xff1a;‘这要是被谁偷偷看了可咋办&#xff1f;’别…