|模擬時鐘(Analog Clock)— Stack 布局與旋轉動畫精講)
一、應用概述模擬時鐘Analog Clock是一款經典的桌面時鐘應用它以傳統鐘表的指針式設計展示當前系統時間。應用通過時針、分針、秒針三根獨立指針的旋轉角度來直觀呈現時間信息同時輔以數字時間顯示兼顧美觀與功能性。項目基于 HarmonyOS 的 ArkTS 語言和聲明式 UI 框架構建完美展示了 Stack 層疊布局、Circle 圓形組件、rotate 旋轉變換以及 setInterval 定時器等核心技術的綜合運用。對于鴻蒙開發者而言模擬時鐘是一個極佳的技術練兵場——它讓你在極少的代碼量中同時掌握坐標定位、中心旋轉、實時刷新三大關鍵能力這些能力廣泛適用于儀表盤、進度指示器、游戲等場景。二、技術架構與組件樹2.1 整體架構Entry Component struct index31 ├── Column (根容器) │ ├── Row (頂部導航欄) │ │ ├── Text (? 返回) │ │ ├── Text ( 模擬時鐘) │ │ └── Blank (彈性空白) │ └── Column (主內容區, layoutWeight1, 居中) │ └── Stack (鐘表盤, 280×280) │ ├── Circle (白色表盤背景) │ ├── Text×12 (刻度數字 I ~ XII) │ ├── Column (時針——旋轉) │ ├── Column (分針——旋轉) │ ├── Column (秒針——旋轉) │ └── Circle (中心圓點)2.2 Entry 與 ComponentEntry Component struct index31 {Entry裝飾器標識該組件為頁面的入口組件允許通過路由直接跳轉。Component聲明這是一個可復用的自定義組件。在 HarmonyOS 中每個頁面通常由一個Entry Component結構體承載。2.3 State 驅動 UI 刷新State time: string 00:00:00; intervalId: number -1;State是 ArkTS 最核心的響應式裝飾器。當time值發生變化時框架自動重新渲染所有依賴time的 UI 部分。intervalId是非響應式的普通變量用于保存定時器句柄避免不必要的渲染。三、生命周期管理3.1 aboutToAppear — 頁面初始化aboutToAppear(): void { this.updateTime(); this.intervalId setInterval((): void { this.updateTime(); }, 1000); }aboutToAppear在組件即將掛載時調用等效于傳統 Android 的onStart或 iOS 的viewDidLoad。這里做了兩件事立即調用updateTime()獲取當前時間避免首次渲染時顯示00:00:00的默認值。啟動定時器每秒更新一次時間。3.2 aboutToDisappear — 資源清理aboutToDisappear(): void { clearInterval(this.intervalId); }當頁面被銷毀如通過router.back()返回上一頁時aboutToDisappear被觸發。務必在此清除定時器否則定時器會持續運行導致內存泄漏甚至崩潰。這是 HarmonyOS 開發中容易被忽視但非常重要的最佳實踐。四、核心代碼深度解析4.1 時間字符串的獲取updateTime(): void { const now new Date(); const hh now.getHours().toString().padStart(2, 0); const mm now.getMinutes().toString().padStart(2, 0); const ss now.getSeconds().toString().padStart(2, 0); this.time hh : mm : ss; }通過標準 JavaScriptDate對象獲取時、分、秒并使用padStart(2, 0)確保始終為兩位數如09:05:03。然后將格式化后的字符串存入State time驅動 UI 更新。4.2 指針角度的計算get hourDeg(): number { const h parseInt(this.time.slice(0, 2)); return (h % 12) * 30 parseInt(this.time.slice(3, 5)) * 0.5; } get minDeg(): number { return parseInt(this.time.slice(3, 5)) * 6; } get secDeg(): number { return parseInt(this.time.slice(6, 8)) * 6; }這三個計算屬性getter是時鐘的核心邏輯秒針角度每秒 6°360° ÷ 60 秒分針角度每分鐘 6°360° ÷ 60 分鐘時針角度每小時 30°360° ÷ 12 小時 每分鐘 0.5°30° ÷ 60 分鐘時針角度疊加了分鐘偏移使得指針能夠在兩小時之間平滑過渡這是區分專業時鐘與簡陋時鐘的關鍵細節。4.3 Stack 層疊布局與刻度定位Stack() { Circle().width(280).height(280).fill(#fff) .shadow({ radius: 12, color: #30000000, offsetY: 4 }) Text(XII).fontSize(16).fontWeight(FontWeight.Bold).fontColor(#333) .position({ x: 128, y: 12 }) Text(III).fontSize(16).fontWeight(FontWeight.Bold).fontColor(#333) .position({ x: 258, y: 130 }) // ... 其余刻度Stack是一個層疊容器所有子組件按聲明順序從下至上堆疊。position({ x, y })使用相對于 Stack 容器左上角的絕對坐標定位坐標原點在容器的 (0,0) 位置。這里有一個精妙的設計鐘表盤尺寸為 280×280中心點為 (140, 140)。12 點刻度 Text(“XII”) 的位置為 (128, 12)即距離頂部 12px水平居中。每個刻度數字的坐標都經過手工計算確保它們均勻分布在圓周上。4.4 rotate 旋轉實現指針轉動// 時針 Column() { Text().width(5).height(70).backgroundColor(#333).borderRadius(3) } .rotate({ angle: this.hourDeg, centerX: 140, centerY: 140 }) .position({ x: 137.5, y: 75 }) // 分針 Column() { Text().width(4).height(100).backgroundColor(#555).borderRadius(2) } .rotate({ angle: this.minDeg, centerX: 140, centerY: 140 }) .position({ x: 138, y: 45 }) // 秒針 Column() { Text().width(2).height(110).backgroundColor(#F44336).borderRadius(1) } .rotate({ angle: this.secDeg, centerX: 140, centerY: 140 }) .position({ x: 139, y: 35 })指針的實現方式非常巧妙——三根指針實際上是三個空的 Column 容器內部只有一個Text()空文本元素通過設置width和height形成一個矩形條。然后通過borderRadius將矩形條兩端變圓模擬指針外觀。.rotate()是實現指針旋轉的核心 APIangle: 旋轉角度度數正值為順時針旋轉centerX/centerY: 旋轉中心這里設置為 (140, 140)即表盤中心指針的position坐標需要精心計算。以時針為例時針長 70px中心在 (140,140)因此時針的頂部起點 y 坐標為140 - 70 5 75加了 5px 的偏移讓指針看起來更自然x 坐標為140 - (5/2) 137.5寬度為 5px需要減去一半來居中。秒針和分針的計算邏輯相同。4.5 中心圓點與數字時間Circle().width(10).height(10).fill(#333) .position({ x: 135, y: 135 })一個直徑 10px 的小圓覆蓋在三根指針的交匯處遮蓋指針底部使視覺效果更加整潔。底部的數字時間使用 Courier New 等寬字體與指針式鐘表形成有趣的對比——既有傳統的模擬顯示又有精確的數字顯示。五、HarmonyOS 特性分析5.1 Stack 層疊布局Stack是 HarmonyOS 中最強大的布局容器之一適用于重疊 UI如本例中的表盤 刻度 指針徽標Badge在頭像右上角顯示未讀消息數遮罩層在圖片上方疊加半透明文字使用Stack時子組件通過position()進行絕對定位或通過alignContent進行相對對齊。在本項目中所有子組件都使用position()精確定位。5.2 Circle 圓形組件Circle是 ArkTS 內置的形狀組件用于繪制正圓或橢圓。相較于通過 CSSborderRadius: 50%模擬圓形的方式Circle更加語義化且性能更優。常用 APIwidth/height設置直徑fill填充顏色stroke/strokeWidth描邊顏色與寬度shadow陰影效果5.3 setInterval / clearInterval 定時器this.intervalId setInterval((): void { this.updateTime(); }, 1000); clearInterval(this.intervalId);setInterval是 HarmonyOS 提供的全局定時器函數返回一個數值類型的 id。執行周期性任務時應當注意將 id 保存在普通成員變量中非State在aboutToDisappear中通過clearInterval清除避免在定時器回調中執行耗時操作超過間隔時間會導致任務堆積六、UI/UX 設計與最佳實踐6.1 視覺設計亮點干凈的白色表盤白色背景配合柔和陰影 (rgba(0,0,0,0.19)) 營造出浮動的立體感。指針差異化時針最粗 (5px) 且顏色最深 (#333)分針適中 (4px, #555)秒針最細 (2px) 且使用醒目的紅色 (#F44336)。這種設計讓用戶能一眼區分三根指針。刻度精確標注12 個主要刻度全部標注沒有使用簡化的四點標注法提升了時鐘的專業感。6.2 最佳實踐實踐說明定時器生命周期管理在aboutToDisappear中clearInterval防止頁面銷毀后定時器繼續運行計算屬性分離邏輯使用 getter 替代在 build 中計算角度使構建函數更簡潔且易于測試不可變數據更新每次更新State time時賦予全新字符串而非修改現有字符串彈性居中layoutWeight(1)justifyContent(FlexAlign.Center)確保內容垂直居中返回導航統一的? 返回樣式和router.back()邏輯保持全應用導航一致性6.3 響應式適配建議當前應用固定了 280×280 的表盤尺寸若要適配不同屏幕建議private clockSize: number Math.min(this.screenWidth, this.screenHeight) * 0.7;然后所有坐標和尺寸都基于this.clockSize進行等比縮放。對于文本刻度可以使用this.clockSize * 0.05等比例方式計算字體大小。七、可擴展思路主題切換支持深色模式、自定義表盤顏色和背景圖片。鬧鐘功能結合ohos.notification和ohos.permission實現鬧鐘提醒。秒表/計時器在頁面內增加 Tab 切換集成秒表和倒計時功能。世界時鐘同時顯示多個時區的時間適合旅行者使用。指針動畫平滑化當前是每秒跳轉一次秒針的跳秒效果可以改為使用animateTo實現平滑過渡。八、總結模擬時鐘應用麻雀雖小五臟俱全僅用 95 行代碼就實現了一個功能完整的鐘表應用。它完美示范了 ArkTS 聲明式 UI 的幾個核心范式響應式狀態管理State自動追蹤數據變化并更新 UI聲明式布局Stackposition實現精確的層疊定位屬性級動畫rotate的 angle 屬性實時變化驅動指針轉動生命周期鉤子aboutToAppear啟動定時器aboutToDisappear清理資源這些知識模塊可以無縫遷移到更復雜的應用場景中。無論你是剛接觸 HarmonyOS 開發的新手還是想要鞏固 ArkTS 基礎的有經驗開發者模擬時鐘都是一個絕佳的練手項目。在下一期的實戰文章中我們將探討一個更具實用價值的工具——間隔計時器Interval Timer看它如何利用同樣的定時器機制為健身訓練提供精確的間歇計時功能。本文是 HarmonyOS 應用開發實戰系列第 31 期更多內容請關注專欄。