用開發(fā)實(shí)戰(zhàn)(第25期)|骰子(Dice Roller)— Unicode 符號(hào)與動(dòng)畫渲染精講)
一、應(yīng)用概述骰子Dice Roller是一款經(jīng)典的休閑娛樂應(yīng)用模擬了真實(shí)擲骰子的過程。應(yīng)用投擲兩個(gè)骰子六面標(biāo)準(zhǔn)骰使用 Unicode 骰面符號(hào)直觀展示每個(gè)骰子的點(diǎn)數(shù)并伴有快速滾動(dòng)的動(dòng)畫效果。每次擲骰后應(yīng)用會(huì)自動(dòng)計(jì)算點(diǎn)數(shù)總和并根據(jù)特殊的點(diǎn)數(shù)組合如蛇眼、Lucky 7、雙六給出趣味描述。應(yīng)用基于 HarmonyOS 的 ArkTS 語言和聲明式 UI 框架構(gòu)建完美展示了 Unicode 特殊符號(hào)渲染、animation 動(dòng)畫屬性、setInterval 定時(shí)器、以及 get 計(jì)算屬性等核心技術(shù)的綜合運(yùn)用。對(duì)于鴻蒙開發(fā)者而言骰子應(yīng)用是一個(gè)展示符號(hào)渲染與動(dòng)畫技巧的絕佳案例——它讓你在約85行代碼中同時(shí)掌握Unicode 符號(hào)映射、定時(shí)器驅(qū)動(dòng)動(dòng)畫、條件描述邏輯、統(tǒng)計(jì)追蹤四大核心能力這些能力廣泛適用于游戲、抽獎(jiǎng)、模擬器等場景。1.1 功能特性雙骰子投擲模擬兩個(gè)六面骰子的隨機(jī)投擲Unicode 骰面顯示使用 ?????? 符號(hào)直觀展示點(diǎn)數(shù)滾動(dòng)動(dòng)畫60ms 間隔快速變化模擬真實(shí)骰子滾動(dòng)點(diǎn)數(shù)總和計(jì)算自動(dòng)計(jì)算兩個(gè)骰子點(diǎn)數(shù)和趣味描述特殊組合2點(diǎn)/3點(diǎn)/7點(diǎn)/12點(diǎn)觸發(fā)趣味文案投擲統(tǒng)計(jì)記錄總共投擲次數(shù)動(dòng)畫鎖定動(dòng)畫進(jìn)行中防止重復(fù)點(diǎn)擊1.2 適用場景桌游電子化輔助工具聚會(huì)活動(dòng)抽獎(jiǎng)環(huán)節(jié)概率統(tǒng)計(jì)教學(xué)演示兒童數(shù)學(xué)啟蒙教育二、技術(shù)架構(gòu)2.1 整體架構(gòu)概覽┌─────────────────────────────────────────┐ │ 數(shù)據(jù)層 │ │ dice1 / dice2 (1~6 整數(shù)) │ │ total (點(diǎn)數(shù)總和) │ │ isRolling (動(dòng)畫狀態(tài)) │ │ rollCount (投擲統(tǒng)計(jì)) │ ├─────────────────────────────────────────┤ │ 狀態(tài)管理層 │ │ State 驅(qū)動(dòng) UI 重新渲染 │ ├─────────────────────────────────────────┤ │ 業(yè)務(wù)邏輯層 │ │ roll() —— 擲骰子 動(dòng)畫 │ │ diceFace() —— 數(shù)字轉(zhuǎn) Unicode 符號(hào) │ │ get d1/d2 —— 動(dòng)畫期間的實(shí)時(shí)隨機(jī)值 │ │ get description —— 特殊組合描述 │ ├─────────────────────────────────────────┤ │ UI 表示層 │ │ 兩個(gè)骰子顯示 / 點(diǎn)數(shù)總和 / 趣味描述 / │ │ 投擲統(tǒng)計(jì) / 擲骰按鈕 │ └─────────────────────────────────────────┘2.2 動(dòng)畫狀態(tài)機(jī)靜止?fàn)顟B(tài) (isRollingfalse) │ │ 點(diǎn)擊擲骰子 ▼ 動(dòng)畫狀態(tài) (isRollingtrue) │ │ setInterval(60ms) × 12次 │ 每次更新 dice1/dice2 為隨機(jī)值 │ UI 顯示實(shí)時(shí)隨機(jī)值 ▼ 判定狀態(tài) (count 12) │ │ clearInterval │ isRolling false │ 計(jì)算 total / description │ rollCount ▼ 靜止?fàn)顟B(tài) (等待下次投擲)三、核心代碼深度分析3.1 完整的頁面源碼import { router } from kit.ArkUI; Entry Component struct index25 { State dice1: number 1; State dice2: number 6; State total: number 7; State isRolling: boolean false; State rollCount: number 0; // 動(dòng)畫期間的實(shí)時(shí)隨機(jī)值 get d1(): number { return this.isRolling ? Math.floor(Math.random() * 6) 1 : this.dice1; } get d2(): number { return this.isRolling ? Math.floor(Math.random() * 6) 1 : this.dice2; } roll() { if (this.isRolling) return; this.isRolling true; let count 0; const intervalId setInterval(() { this.dice1 Math.floor(Math.random() * 6) 1; this.dice2 Math.floor(Math.random() * 6) 1; count; if (count 12) { clearInterval(intervalId); this.isRolling false; this.total this.dice1 this.dice2; this.rollCount; } }, 60); } // 數(shù)字轉(zhuǎn) Unicode 骰面符號(hào) diceFace(val: number): string { const faces [?, ?, ?, ?, ?, ?]; return faces[val - 1]; } // 趣味描述 get description(): string { const t this.total; if (t 2) return 蛇眼; if (t 7) return Lucky 7!; if (t 12) return 雙六; if (t 3) return 霉運(yùn); return ; } build() { Column() { Row() { Text(? 返回).fontSize(16).fontColor(#0077FF).onClick(() router.back()) Text( 骰子).fontSize(18).fontWeight(FontWeight.Bold) Blank() }.width(92%).padding({ top: 16, bottom: 8 }) Column() { // 雙骰子展示 Row({ space: 20 }) { Column() { Text(this.diceFace(this.isRolling ? this.d1 : this.dice1)) .fontSize(80).animation({ duration: 60 }) Text(骰子 1).fontSize(14).fontColor(#888) } Column() { Text(this.diceFace(this.isRolling ? this.d2 : this.dice2)) .fontSize(80).animation({ duration: 60 }) Text(骰子 2).fontSize(14).fontColor(#888) } }.margin({ bottom: 16 }) // 點(diǎn)數(shù)顯示 Text(點(diǎn)數(shù): ${this.isRolling ? ... : this.total}) .fontSize(36).fontWeight(FontWeight.Bold).fontColor(#F44336) // 趣味描述 Text(this.description).fontSize(18).fontColor(#FF9800).margin({ top: 4 }) // 投擲統(tǒng)計(jì) Text(已擲 ${this.rollCount} 次).fontSize(14).fontColor(#888).margin({ top: 8 }) // 擲骰按鈕 Button( 擲骰子).width(200).height(56).borderRadius(28) .backgroundColor(#4CAF50).fontSize(22).fontWeight(FontWeight.Bold) .margin({ top: 16 }).onClick(() this.roll()) } .layoutWeight(1).justifyContent(FlexAlign.Center) } .width(100%).height(100%).backgroundColor(#f8f8f8) } }3.2 Unicode 骰面符號(hào)映射diceFace(val: number): string { const faces [?, ?, ?, ?, ?, ?]; return faces[val - 1]; }這是本應(yīng)用最獨(dú)特的設(shè)計(jì)——使用 Unicode 的彌生骰子符號(hào)Miscellaneous Symbols來展示骰面數(shù)字Unicode字符描述1U2680?白色骰子12U2681?白色骰子23U2682?白色骰子34U2683?白色骰子45U2684?白色骰子56U2685?白色骰子6這些 Unicode 字符在支持它們的字體中會(huì)渲染為帶點(diǎn)陣的骰面圖案無需使用圖片資源即可獲得良好的視覺效果。這種純文本符號(hào)的方案比圖片方案具有以下優(yōu)勢無資源依賴不需要額外加載圖片減小應(yīng)用體積矢量縮放任意放大倍數(shù)下保持清晰無鋸齒主題適配顏色可以通過 CSS 屬性自由控制3.3 動(dòng)畫期間實(shí)時(shí)隨機(jī)值get d1(): number { return this.isRolling ? Math.floor(Math.random() * 6) 1 : this.dice1; } get d2(): number { return this.isRolling ? Math.floor(Math.random() * 6) 1 : this.dice2; }這是一個(gè)精妙的設(shè)計(jì)——在動(dòng)畫期間每次 UI 渲染時(shí)都實(shí)時(shí)生成新的隨機(jī)值而非等待setInterval更新State變量。這樣做的好處更高的刷新率脫離 60ms 定時(shí)器的限制隨 UI 渲染幀率同步更新更流暢的動(dòng)畫渲染引擎每幀都看到新值視覺上更加流暢資源節(jié)約無需額外的定時(shí)器管理動(dòng)畫結(jié)束后的判定邏輯if (count 12) { clearInterval(intervalId); this.isRolling false; // 停止實(shí)時(shí)隨機(jī) this.total this.dice1 this.dice2; this.rollCount; }當(dāng)isRolling設(shè)為false后get d1()返回固定的this.dice1值即setInterval最后一次設(shè)置的值骰子顯示定格。3.4 animation 動(dòng)畫屬性Text(this.diceFace(...)) .fontSize(80).animation({ duration: 60 })animation()是 HarmonyOS 中用于為組件添加過渡動(dòng)畫的屬性方法。它的參數(shù)是一個(gè)AnimationOptions對(duì)象包含以下常用屬性duration動(dòng)畫持續(xù)時(shí)間毫秒默認(rèn)1000curve動(dòng)畫曲線Curve.EaseInOut等默認(rèn)Curve.Easedelay動(dòng)畫延遲開始的時(shí)間毫秒iterations動(dòng)畫重復(fù)次數(shù)-1表示無限循環(huán)playMode播放模式PlayMode.Normal/PlayMode.Reverse/ …在本應(yīng)用中animation({ duration: 60 })設(shè)置為60ms的過渡動(dòng)畫。當(dāng)骰子符號(hào)從 ? 變?yōu)?? 時(shí)這個(gè)過渡動(dòng)畫讓變化過程更加平滑而非生硬的瞬間切換。需要注意的是這里的animation屬性作用于組件屬性變化時(shí)的過渡效果。當(dāng)diceFace()的返回值變化時(shí)文字內(nèi)容變化動(dòng)畫會(huì)嘗試在變化前后之間進(jìn)行過渡。但對(duì)于文字內(nèi)容變化過渡效果可能不如數(shù)值屬性如width、opacity、rotate等明顯。3.5 趣味描述邏輯get description(): string { const t this.total; if (t 2) return 蛇眼; // 兩個(gè)1點(diǎn) if (t 7) return Lucky 7!; // 最常見的點(diǎn)數(shù) if (t 12) return 雙六; // 兩個(gè)6點(diǎn) if (t 3) return 霉運(yùn); // 12 最小非蛇眼組合 return ; // 其他組合不顯示描述 }四種特殊組合的判定規(guī)則蛇眼Snake Eyes兩個(gè)骰子都是1點(diǎn)總和2 —— 骰子中最小的組合名字源于兩個(gè)1點(diǎn)看起來像蛇的眼睛Lucky 7總和7 —— 兩個(gè)骰子點(diǎn)數(shù)和為7的概率最高1/6被視為幸運(yùn)數(shù)字雙六Boxcars兩個(gè)骰子都是6點(diǎn)總和12 —— 最大的組合霉運(yùn)總和312—— 僅次于蛇眼的壞運(yùn)氣組合其他的組合4、5、6、8、9、10、11不顯示趣味描述保持界面簡潔。四、HarmonyOS 特性深度解析4.1 get 計(jì)算屬性的實(shí)時(shí)求值機(jī)制本應(yīng)用中使用了三個(gè)get計(jì)算屬性d1、d2、description它們在每次 UI 渲染時(shí)被重新求值。這種機(jī)制的關(guān)鍵理解是// 在模板中引用 get 屬性 Text(this.diceFace(this.isRolling ? this.d1 : this.dice1))ArkTS 的渲染引擎在每次渲染周期中檢測到State變量變化如isRolling從false變?yōu)閠rue觸發(fā)重新渲染重新求值所有模板表達(dá)式計(jì)算this.d1→ 調(diào)用get d1()→ 如果是動(dòng)畫狀態(tài)則生成隨機(jī)值計(jì)算this.diceFace(隨機(jī)值)→ 映射為 Unicode 符號(hào)更新 UI 顯示這個(gè)鏈條中g(shù)et d1()在動(dòng)畫期間每次渲染都生成不同的隨機(jī)值產(chǎn)生了持續(xù)的視覺變化。4.2 setInterval 的正確清理時(shí)機(jī)roll() { if (this.isRolling) return; this.isRolling true; let count 0; const intervalId setInterval(() { // ... if (count 12) { clearInterval(intervalId); // 在回調(diào)內(nèi)部清理 this.isRolling false; } }, 60); }定時(shí)器的清理發(fā)生在回調(diào)函數(shù)內(nèi)部通過count計(jì)數(shù)達(dá)到閾值時(shí)。這種自清理模式的好處確定性清理清理時(shí)機(jī)明確且可控?zé)o需外部干預(yù)不需要在aboutToDisappear中額外清理避免內(nèi)存泄漏定時(shí)器在完成任務(wù)后立即釋放相比而言如果定時(shí)器是持續(xù)運(yùn)行的如時(shí)鐘應(yīng)用則必須在aboutToDisappear中清理。4.3 條件渲染的不同模式本應(yīng)用在三個(gè)地方使用了條件渲染展示了三種不同的場景// 模式1三元表達(dá)式 - 用于文字內(nèi)容 Text(點(diǎn)數(shù): ${this.isRolling ? ... : this.total}) // 模式2三元表達(dá)式 - 用于 get 屬性選擇 Text(this.diceFace(this.isRolling ? this.d1 : this.dice1)) // 模式3get 屬性條件返回 - 用于描述文字 get description(): string { if (...) return ...; return ; }這三種模式分別適用于內(nèi)聯(lián)條件簡單的文字內(nèi)容切換值選擇器根據(jù)狀態(tài)選擇不同的數(shù)據(jù)源計(jì)算屬性復(fù)雜的多分支邏輯4.4 綠色主題的按鈕設(shè)計(jì)Button( 擲骰子).width(200).height(56).borderRadius(28) .backgroundColor(#4CAF50).fontSize(22).fontWeight(FontWeight.Bold)按鈕使用了 Material Design 的綠色#4CAF50在心理學(xué)上綠色代表開始和行動(dòng)。大尺寸200×56、大圓角28px和大字體22px的設(shè)計(jì)讓按鈕成為頁面中最重要的視覺焦點(diǎn)吸引用戶點(diǎn)擊。五、UI/UX 設(shè)計(jì)模式5.1 頁面布局結(jié)構(gòu)┌─────────────────────────────────┐ │ Row: 導(dǎo)航欄 │ ├─────────────────────────────────┤ │ Column: 內(nèi)容區(qū) (居中) │ │ ├── Row: 兩個(gè)骰子 │ │ │ ├── 骰子1 (?~?) │ │ │ └── 骰子2 (?~?) │ │ ├── Text: 點(diǎn)數(shù)總和 (大號(hào)紅色) │ │ ├── Text: 趣味描述 (橙色) │ │ ├── Text: 投擲次數(shù) (灰色) │ │ └── Button: 擲骰子 │ └─────────────────────────────────┘5.2 視覺層次與色彩搭配元素顏色作用骰子符號(hào)默認(rèn)黑色核心視覺元素80px超大字號(hào)點(diǎn)數(shù)總和#F44336紅色最重要的結(jié)果信息趣味描述#FF9800橙色次要的趣味性信息擲骰按鈕#4CAF50綠色行動(dòng)召喚CTA統(tǒng)計(jì)次數(shù)#888灰色輔助信息5.3 信息層級(jí)設(shè)計(jì)信息按照重要性從上到下排列骰子符號(hào)最大最醒目—— 核心視覺點(diǎn)數(shù)總和36px紅色—— 核心數(shù)據(jù)趣味描述18px橙色—— 情感化反饋投擲次數(shù)14px灰色—— 輔助統(tǒng)計(jì)每個(gè)層級(jí)之間通過margin、字號(hào)和顏色差異形成清晰的區(qū)分。六、最佳實(shí)踐與經(jīng)驗(yàn)總結(jié)6.1 隨機(jī)數(shù)的正確生成Math.floor(Math.random() * 6) 1這是生成 1~6 范圍內(nèi)隨機(jī)整數(shù)的標(biāo)準(zhǔn)寫法。分解如下Math.random()生成 [0, 1) 區(qū)間的浮點(diǎn)數(shù)含0不含1* 6擴(kuò)展到 [0, 6) 區(qū)間Math.floor()向下取整到 [0, 5] 整數(shù) 1偏移到 [1, 6]重要提示在鴻蒙開發(fā)中Math.random()是偽隨機(jī)數(shù)生成器對(duì)于游戲類應(yīng)用足夠了。6.2 動(dòng)畫期間的防重入roll() { if (this.isRolling) return; this.isRolling true; // ... }isRolling標(biāo)志位實(shí)現(xiàn)了按鈕防重入。在動(dòng)畫進(jìn)行中即使用戶多次點(diǎn)擊擲骰子按鈕roll()方法也會(huì)在入口處直接返回不會(huì)啟動(dòng)新的動(dòng)畫循環(huán)。這是交互設(shè)計(jì)中防止意外操作的關(guān)鍵機(jī)制。6.3 Unicode 符號(hào)的兼容性Unicode 骰面符號(hào)U2680~U2685屬于 Unicode 3.0 標(biāo)準(zhǔn)在現(xiàn)代設(shè)備和操作系統(tǒng)中都有良好的支持。但在某些老舊設(shè)備或不支持這些符號(hào)的字體中可能會(huì)顯示為空白或方框。在生產(chǎn)環(huán)境中建議提供降級(jí)方案檢測符號(hào)渲染情況如果無法正常顯示則使用數(shù)字替代使用自定義字體打包包含這些符號(hào)的字體文件圖片備選在極端情況下回退到圖片顯示6.4 代碼組織建議對(duì)于更復(fù)雜的骰子應(yīng)用可以考慮以下優(yōu)化方向多種骰子類型支持4面D4、8面D8、10面D10、12面D12、20面D20等角色扮演游戲常用骰子投擲歷史記錄保存最近幾次投擲的結(jié)果統(tǒng)計(jì)圖表展示點(diǎn)數(shù)分布的概率統(tǒng)計(jì)shake 傳感器利用設(shè)備加速度傳感器通過搖動(dòng)手機(jī)觸發(fā)擲骰聯(lián)機(jī)對(duì)戰(zhàn)通過分布式能力實(shí)現(xiàn)多人擲骰比大小七、總結(jié)骰子應(yīng)用在約85行代碼中精彩展示了 ArkTS 聲明式 UI 開發(fā)的多個(gè)核心概念Unicode 符號(hào)映射使用 ?????? 純文本方式渲染骰面get 實(shí)時(shí)隨機(jī)值動(dòng)畫期間每次渲染生成新隨機(jī)數(shù)setInterval 定時(shí)器動(dòng)畫60ms 間隔的快速滾動(dòng)效果animation 過渡動(dòng)畫屬性變化的平滑過渡防重入機(jī)制isRolling 標(biāo)志位的按鈕鎖定條件描述邏輯特殊點(diǎn)數(shù)組合的趣味反饋這個(gè)應(yīng)用以擲骰子這一簡單的游戲需求為切入點(diǎn)展示了純文本符號(hào)渲染和定時(shí)器動(dòng)畫的核心技術(shù)。它不僅是桌游愛好者的實(shí)用工具更是學(xué)習(xí) ArkTS 動(dòng)畫編程和符號(hào)處理的優(yōu)秀教學(xué)案例。簡潔的代碼背后蘊(yùn)含著豐富的交互設(shè)計(jì)智慧和性能優(yōu)化技巧。