鴻蒙 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,              // 振動器IDusage: '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 = playerthis.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')}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/72026.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/72026.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/72026.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

神經性肺纖維的預防方法

神經性肺纖維的預防方法 一、引言 神經性肺纖維化是一種慢性進行性肺部疾病&#xff0c;其病因復雜&#xff0c;包括遺傳、環境等多種因素。該病不僅影響患者的呼吸功能&#xff0c;還可能對神經系統造成損害。因此&#xff0c;預防神經性肺纖維化顯得尤為重要。本文將詳細介…

azure sql 網絡安全組 網絡安全sql注入

&#x1f345; 點擊文末小卡片 &#xff0c;免費獲取網絡安全全套資料&#xff0c;資料在手&#xff0c;漲薪更快 SQL注入 1、原理 針對注入的攻擊行為可描述為通過用戶可控參數中注入SQL語法&#xff0c;破壞原有SQL結構&#xff0c;達到編寫程序意料之外結果的攻擊行為。 其…

【Day50 LeetCode】圖論問題 Ⅷ

一、圖論問題 Ⅷ 1、dijkstra算法 堆優化 采用堆來優化&#xff0c;適合節點多的稀疏圖。代碼如下&#xff1a; # include<iostream> # include<vector> # include<list> # include<queue> # include<climits>using namespace std;class myco…

利用node.js搭配express框架寫后端接口(一)

Node.js 憑借其高效的非阻塞 I/O 操作、事件驅動架構以及輕量級的特點&#xff0c;成為了開發高性能服務器應用的熱門選擇。Express 框架作為 Node.js 上最流行的 Web 應用框架之一&#xff0c;以其簡潔的 API 和豐富的中間件生態系統&#xff0c;極大地簡化了 Web 后端開發流程…

【小白數學】為什么可以用拉格朗日乘子法求函數的極值【二】

我們在上一篇【小白數學】- 為什么可以用拉格朗日乘子法求函數的極值【一】已經介紹了一種較為“嚴謹“的方法來說明為什么拉格朗日乘子法可以幫助我們求具有等式約束條件下的函數的極值。雖然在我們的例子中”等式約束“中只有一個等式。但其實很容易推廣到多個等式約束的情況…

JAVA面試_進階部分_netty面試題

1.BIO、NIO 和 AIO 的區別&#xff1f; BIO&#xff1a;一個連接一個線程&#xff0c;客戶端有連接請求時服務器端就需要啟動一個線程進行處理。線程開銷大。 偽異步 IO&#xff1a;將請求連接放入線程池&#xff0c;一對多&#xff0c;但線程還是很寶貴的資源。 NIO&#x…

考研出分24小時,人類精神狀態圖鑒

2月24日&#xff0c;上午10點起&#xff0c;各省考研初試成績陸續公布&#xff0c;考生們或緊張的輸入準考證號&#xff0c;或抱團等待“審判”。然而更魔幻的還在后頭——下午4點&#xff0c;教育部竟在同一天直接發布了《2025年研考國家分數線》。 不少網友表示&#xff1a;…

川翔云電腦優勢總結

在數字化時代&#xff0c;川翔云電腦依托云計算技術&#xff0c;為用戶解決硬件性能瓶頸問題。川翔云電腦使用云渲碼&#xff1a;【2355】 卓越硬件配置&#xff1a;配備 RTX 3090、48G 顯存的 RTX 4090plus&#xff0c;支持 1 - 8 卡機配置&#xff0c;多卡并行計算能力強&am…

DeepSeek開源周Day4:三連發!突破 AI 訓練瓶頸的立體解決方案,并行計算三劍客DualPipe、EPLB與Profile-data

項目地址&#xff1a; https://github.com/deepseek-ai/DualPipehttps://github.com/deepseek-ai/eplbhttps://github.com/deepseek-ai/profile-data 開源日歷&#xff1a;2025-02-24起 每日9AM(北京時間)更新&#xff0c;持續五天 (4/5)&#xff01; ? ? 一、背景概述 …

基于W2605C語音識別合成芯片的智能語音交互鬧鐘方案-AI對話享受智能生活

隨著科技的飛速發展&#xff0c;智能家居產品正逐步滲透到我們的日常生活中&#xff0c;其中智能鬧鐘作為時間管理的得力助手&#xff0c;也在不斷進化。基于W2605C語音識別與語音合成芯片的智能語音交互鬧鐘&#xff0c;憑借其強大的聯網能力、自動校時功能、實時天氣獲取、以…

Vite與Turbopack現代構建工具架構解析:秒級構建的性能奧秘

引言&#xff1a;傳統構建工具的效能瓶頸 Shopify將前端倉庫遷移至Vite后&#xff0c;HMR更新時間從Webpack的4.2秒縮短至48毫秒。Turbopack在Vercel生產環境測試中&#xff0c;增量構建速度較Webpack快700%。ChromeOS團隊采用Vite后&#xff0c;生產構建從Webpack的17分鐘優化…

網絡基礎知識-2

N個節點完全互聯的網型網即N個節點的無向完全圖&#xff0c;無向完全圖的邊數計算如下&#xff1a;每個節點都要指向其他N-1個節點&#xff0c;但是因為無向兩個節點之間的邊會重復&#xff0c;因此有N(N-1)/2條邊HDLC&#xff08;高級數據鏈路控制協議&#xff09;是一種面向比…

視頻級虛擬試衣技術在淘寶的產品化實踐

作為一種新的商品表現形態&#xff0c;內容幾乎存在于手淘用戶動線全流程&#xff0c;例如信息流種草內容、搜索消費決策內容、詳情頁種草內容等。通過低成本、高時效的AIGC內容生成能力&#xff0c;能夠從供給端緩解內容生產成本高的問題&#xff0c;通過源源不斷的低成本供給…

藍橋備賽(三)- 條件判斷與循環(下)

一、for循環 1.1 for 循環語法形式 for 循環是三種循環中使用最多的 &#xff0c; for 循環的語法形式如下&#xff1a; 1.2 執行流程 for 循環中 &#xff0c; 表達式1&#xff08;初始化&#xff09;只執行一次 &#xff01; 1.3 實踐 練習&#xff1a;使用 for 循環在屏幕…

VMware Fusion 虛擬機Mac版 安裝CentOS 7 系統

介紹 CentOS是Community Enterprise Operating System的縮寫&#xff0c;也叫做社區企業操作系統。是企業Linux發行版領頭羊Red Hat Enterprise Linux的再編譯版本&#xff08;是一個再發行版本&#xff09;&#xff0c;而且在RHEL的基礎上修正了不少已知的 Bug &#xff0c;相…

如果更換ip地址會怎么樣?網絡ip地址怎么更換

IP地址&#xff0c;作為網絡設備的數字身份證&#xff0c;其穩定性和安全性對于網絡通訊至關重要。然而&#xff0c;在某些特定情況下&#xff0c;我們可能需要更換設備的IP地址&#xff0c;以滿足安全、隱私或網絡管理的需求。那么&#xff0c;如果更換IP地址會怎么樣&#xf…

網絡通信/IP網絡劃分/子網掩碼的概念和使用

文章目錄 概述子網的考題子網掩碼的歷史有/無類地址子網劃分!子網掩碼超網技術/CIDR子網掩碼和路由IP子網掩碼定義 網絡規劃網絡規劃-拆子網網絡規劃-組超網子網劃分案例 區分于其他特殊IP地址IP地址和網絡地址子網掩碼和網絡地址子網掩碼和廣播地址 子網間的通信其他 概述 本…

評估自動駕駛(AD)策略性能的關鍵指標

以下是針對自動駕駛&#xff08;AD&#xff09;策略性能評測指標的詳細解讀&#xff0c;結合其物理意義與工程價值&#xff1a; 核心評測指標分類與含義 1. 安全性指標&#xff08;Safety&#xff09; 動態碰撞率&#xff08;Dynamic Collision Ratio, DCR&#xff09; 定義&a…

C++11相較于C++98的新特性介紹:列表初始化,右值引用與移動語義

一&#xff0c;列表初始化 1.1C98中傳統的{} C98中一般數組和結構體可以使用{}進行初始化&#xff1a; struct Date {int _year;int _month;int _day; };int main() {int a[] { 1,2,3,4,5 };Date _date { 2025,2,27 };return 0; } 1.2C11中的{} C11以后想統一初始化方式&…

序列化是什么?常見的序列化方式有哪些?什么時候我們會用到序列化?

序列化&#xff08;Serialization&#xff09;是指將對象的狀態信息轉換為可以存儲或傳輸的形式&#xff08;如字節序列、XML 文檔、JSON 字符串等&#xff09;的過程。反序列化則是序列化的逆過程&#xff0c;它將存儲或接收到的字節序列、XML 文檔、JSON 字符串等轉換回對象的…