鴻蒙NEXT開發案例:轉盤1W

ocenwimtaegrad發表於2024-11-10

【1】引言(完整程式碼在最後面)

在鴻蒙NEXT系統中,開發一個有趣且實用的轉盤應用不僅可以提升使用者體驗,還能展示鴻蒙系統的強大功能。本文將詳細介紹如何使用鴻蒙NEXT系統開發一個轉盤應用,涵蓋從元件定義到使用者互動的完整過程。

【2】環境準備

電腦系統:windows 10

開發工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806

工程版本:API 12

真機:mate60 pro

語言:ArkTS、ArkUI

【3】難點分析

1. 扇形路徑的計算

難點:建立扇形的路徑需要精確計算起始點、結束點和弧線引數。尤其是涉及到三角函式的使用,初學者可能會對如何將角度轉換為座標感到困惑。

解決方案:可以透過繪製簡單的示意圖來幫助理解扇形的構造,並在程式碼中新增詳細註釋,解釋每一步的計算過程。

2. 動態角度計算

難點:在轉盤旋轉時,需要根據單元格的比例動態計算每個單元格的角度和旋轉角度。這涉及到累加和比例計算,可能會導致邏輯錯誤。

解決方案:使用陣列的 reduce 方法來計算總比例,並在計算每個單元格的角度時,確保邏輯清晰。可以透過單元測試來驗證每個單元格的角度是否正確。

3. 動畫效果的實現

難點:實現轉盤的旋轉動畫需要對動畫的持續時間、曲線和結束後的狀態進行管理。初學者可能會對如何控制動畫的流暢性和效果感到困惑。

解決方案:可以參考鴻蒙NEXT的動畫文件,瞭解不同的動畫效果和引數設定。透過逐步除錯,觀察動畫效果並進行調整。

4. 使用者互動的處理

難點:處理使用者點選事件,尤其是在動畫進行時,如何禁用按鈕以防止重複點選,可能會導致狀態管理的複雜性。

解決方案:在按鈕的點選事件中,使用狀態變數(如 isAnimating)來控制按鈕的可用性,並在動畫結束後恢復按鈕的狀態。

5. 元件的狀態管理

難點:在多個元件之間傳遞狀態(如當前選中的單元格、轉盤的角度等)可能會導致狀態管理混亂。

解決方案:使用狀態管理工具(如 @State 和 @Trace)來確保狀態的統一管理,並在需要的地方進行狀態更新,保持元件之間的解耦。

【完整程式碼】

?

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 import { CounterComponent, CounterType } from '@kit.ArkUI'``; // 匯入計數器元件和計數器型別 // 定義扇形元件``@Component``struct Sector {``@Prop radius: number; // 扇形的半徑``@Prop angle: number; // 扇形的角度``@Prop color: string; // 扇形的顏色 // 建立扇形路徑的函式``createSectorPath(radius: number, angle: number): string {``const centerX = radius / 2; // 計算扇形中心的X座標``const centerY = radius / 2; // 計算扇形中心的Y座標``const startX = centerX; // 扇形起始點的X座標``const startY = centerY - radius; // 扇形起始點的Y座標``const halfAngle = angle / 4; // 計算半個角度 // 計算扇形結束點1的座標``const endX1 = centerX + radius * Math.cos((halfAngle * Math.PI) / 180);``const endY1 = centerY - radius * Math.sin((halfAngle * Math.PI) / 180); // 計算扇形結束點2的座標``const endX2 = centerX + radius * Math.cos((-halfAngle * Math.PI) / 180);``const endY2 = centerY - radius * Math.sin((-halfAngle * Math.PI) / 180); // 判斷是否為大弧``const largeArcFlag = angle / 2 > 180 ? 1 : 0;``const sweepFlag = 1; // 設定弧線方向為順時針 // 生成SVG路徑命令``const pathCommands =```M${startX} ${startY} A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${endX1} ${endY1} L${centerX} ${centerY} L${endX2} ${endY2} A${radius} ${radius} 0 ${largeArcFlag} ${1 -``sweepFlag} ${startX} ${startY} Z;return` `pathCommands;` `// 返回路徑命令} // 構建扇形元件build() {Stack() {// 建立第一個扇形路徑Path().width(`${this.radius}px`)` `// 設定寬度為半徑.height(${``this``.radius}px) // 設定高度為半徑.commands(this.createSectorPath(this.radius,` `this.angle)) // 設定路徑命令.fillOpacity(1)` `// 設定填充透明度.fill(this.color) // 設定填充顏色.strokeWidth(0)` `// 設定邊框寬度為0.rotate({ angle: this.angle / 4 - 90 });` `// 旋轉扇形` `// 建立第二個扇形路徑Path().width(`${this.radius}px`)` `// 設定寬度為半徑.height(${``this``.radius}px) // 設定高度為半徑.commands(this.createSectorPath(this.radius,` `this.angle)) // 設定路徑命令.fillOpacity(1)` `// 設定填充透明度.fill(this.color) // 設定填充顏色.strokeWidth(0)` `// 設定邊框寬度為0.rotate({ angle: 180 - (this.angle / 4 - 90) }); // 旋轉扇形}}}` `// 定義單元格類@ObservedV2class` `Cell {@Trace angle: number = 0; // 扇形的角度@Trace title: string;` `// 當前格子的標題@Trace color: string; // 背景顏色@Trace rotate: number = 0;` `// 在轉盤要旋轉的角度angleStart: number = 0; // 輪盤所在區間的起始angleEnd: number = 0;` `// 輪盤所在區間的結束proportion: number = 0; // 所佔比例 // 建構函式constructor(proportion: number, title: string, color: string) {this.proportion = proportion;` `// 設定比例this.title = title;` `// 設定標題this.color = color;` `// 設定顏色}}` `// 定義轉盤元件@Entry@Componentstruct Wheel {@State cells: Cell[] = [];` `// 儲存單元格的陣列@State wheelWidth: number = 600; // 轉盤的寬度@State currentAngle: number = 0;` `// 當前轉盤的角度@State selectedName: string = "";` `// 選中的名稱isAnimating: boolean = false;` `// 動畫狀態colorIndex: number = 0; // 顏色索引colorPalette: string[] = [` `// 顏色調色盤"#26c2ff","#978efe","#c389fe","#ff85bd","#ff7051","#fea800","#ffcf18","#a9c92a"];` `// 元件即將出現時呼叫aboutToAppear(): void {// 初始化單元格this.cells.push(new Cell(1, "跑步",` `this.colorPalette[this.colorIndex++ % this.colorPalette.length]));this.cells.push(new Cell(2, "跳繩",` `this.colorPalette[this.colorIndex++ %` `this.colorPalette.length]));this.cells.push(new` `Cell(1,` `"唱歌",` `this.colorPalette[this.colorIndex++ %` `this.colorPalette.length]));this.cells.push(new` `Cell(4,` `"跳舞",` `this.colorPalette[this.colorIndex++ %` `this.colorPalette.length]));` `this.calculateAngles();` `// 計算角度}` `// 計算每個單元格的角度private` `calculateAngles() {// 根據比例計算總比例const totalProportion =` `this.cells.reduce((sum, cell) => sum + cell.proportion, 0);this.cells.forEach(cell => {cell.angle = (cell.proportion * 360) / totalProportion;` `// 計算每個單元格的角度});` `let` `cumulativeAngle = 0;` `// 累計角度this.cells.forEach(cell => {cell.angleStart = cumulativeAngle;` `// 設定起始角度cumulativeAngle += cell.angle;` `// 更新累計角度cell.angleEnd = cumulativeAngle;` `// 設定結束角度cell.rotate = cumulativeAngle - (cell.angle / 2);` `// 計算旋轉角度});}` `// 構建轉盤元件build() {Column() {Row() {Text('轉盤').fontSize(20).fontColor("#0b0e15");` `// 顯示轉盤標題}.width('100%').height(44).justifyContent(FlexAlign.Center);` `// 設定行的寬度和高度` `// 顯示當前狀態Text(this.isAnimating ?` `'旋轉中'` `: `${this.selectedName}`).fontSize(20).fontColor("#0b0e15").height(40);` `Stack() {Stack() {// 遍歷每個單元格並繪製扇形ForEach(this.cells, (cell: Cell) => {Stack() {Sector({ radius: lpx2px(this.wheelWidth) / 2, angle: cell.angle, color: cell.color });` `// 建立扇形Text(cell.title).fontColor(Color.White).margin({ bottom: `${this.wheelWidth / 1.4}lpx` });` `// 顯示單元格標題}.width('100%').height('100%').rotate({ angle: cell.rotate });` `// 設定寬度和高度,並旋轉});}.borderRadius('50%')` `// 設定圓角.backgroundColor(Color.Gray)` `// 設定背景顏色.width(`${this.wheelWidth}lpx`)` `// 設定轉盤寬度.height(`${this.wheelWidth}lpx`)` `// 設定轉盤高度``.rotate({ angle:` `this``.currentAngle });` `// 旋轉轉盤` `// 建立指標``Polygon({ width: 20, height: 10 })``.points([[0, 0], [10, -20], [20, 0]])` `// 設定指標的點``.fill(``"#d72b0b"``)` `// 設定指標顏色``.height(20)` `// 設定指標高度``.margin({ bottom:` `'140lpx'` `});` `// 設定指標底部邊距` `// 建立開始按鈕``Button(``'開始'``)``.fontColor(``"#c53a2c"``)` `// 設定按鈕字型顏色``.borderWidth(10)` `// 設定按鈕邊框寬度``.borderColor(``"#dd2218"``)` `// 設定按鈕邊框顏色``.backgroundColor(``"#fde427"``)` `// 設定按鈕背景顏色``.width(``'200lpx'``)` `// 設定按鈕寬度``.height(``'200lpx'``)` `// 設定按鈕高度``.borderRadius(``'50%'``)` `// 設定按鈕為圓形``.clickEffect({ level: ClickEffectLevel.LIGHT })` `// 設定點選效果``.onClick(() => {` `// 點選按鈕時的回撥函式``if` `(``this``.isAnimating) {` `// 如果正在動畫中,返回``return``;``}``this``.selectedName =` `""``;` `// 清空選中的名稱``this``.isAnimating =` `true``;` `// 設定動畫狀態為正在動畫``animateTo({` `// 開始動畫``duration: 5000,` `// 動畫持續時間為5000毫秒``curve: Curve.EaseInOut,` `// 動畫曲線為緩入緩出``onFinish: () => {` `// 動畫完成後的回撥``this``.currentAngle %= 360;` `// 保持當前角度在0到360之間``for` `(const cell of` `this``.cells) {` `// 遍歷每個單元格``// 檢查當前角度是否在單元格的角度範圍內``if` `(360 -` `this``.currentAngle >= cell.angleStart && 360 -` `this``.currentAngle <= cell.angleEnd) {``this``.selectedName = cell.title;` `// 設定選中的名稱為當前單元格的標題``break``;` `// 找到後退出迴圈``}``}``this``.isAnimating =` `false``;` `// 設定動畫狀態為未動畫``},``}, () => {` `// 動畫進行中的回撥``this``.currentAngle += (360 * 5 + Math.floor(Math.random() * 360));` `// 更新當前角度,增加隨機旋轉``});``});``}` `// 建立滾動區域``Scroll() {``Column() {``// 遍歷每個單元格,建立輸入框和計數器``ForEach(``this``.cells, (item: Cell, index: number) => {``Row() {``// 建立文字輸入框,顯示單元格標題``TextInput({ text: item.title })``.layoutWeight(1)` `// 設定輸入框佔據剩餘空間``.onChange((value) => {` `// 輸入框內容變化時的回撥``item.title = value;` `// 更新單元格標題``});``// 建立計數器元件``CounterComponent({``options: {``type: CounterType.COMPACT,` `// 設定計數器型別為緊湊型``numberOptions: {``label: `當前佔比`,` `// 設定計數器標籤``value: item.proportion,` `// 設定計數器初始值``min: 1,` `// 設定最小值``max: 100,` `// 設定最大值``step: 1,` `// 設定步長``onChange: (value: number) => {` `// 計數器值變化時的回撥``item.proportion = value;` `// 更新單元格的比例``this``.calculateAngles();` `// 重新計算角度``}``}``}``});``// 建立刪除按鈕``Button(``'刪除'``).onClick(() => {``this``.cells.splice(index, 1);` `// 從單元格陣列中刪除當前單元格``this``.calculateAngles();` `// 重新計算角度``});``}.width(``'100%'``).justifyContent(FlexAlign.SpaceBetween)` `// 設定行的寬度和內容對齊方式``.padding({ left: 40, right: 40 });` `// 設定左右內邊距``});``}.layoutWeight(1);` `// 設定滾動區域佔據剩餘空間``}.layoutWeight(1)` `// 設定滾動區域佔據剩餘空間``.margin({ top: 20, bottom: 20 })` `// 設定上下外邊距``.align(Alignment.Top);` `// 設定對齊方式為頂部對齊` `// 建立新增新內容按鈕``Button(``'新增新內容'``).onClick(() => {``// 向單元格陣列中新增新單元格``this``.cells.push(``new` `Cell(1,` `"新內容"``,` `this``.colorPalette[``this``.colorIndex++ %` `this``.colorPalette.length]));``this``.calculateAngles();` `// 重新計算角度``}).margin({ top: 20, bottom: 20 });` `// 設定按鈕的上下外邊距``}``.height(``'100%'``)` `// 設定元件高度為100%``.width(``'100%'``)` `// 設定元件寬度為100%``.backgroundColor(``"#f5f8ff"``);` `// 設定元件背景顏色``}``}`

  

本部落格參考FlowerCloud機場。轉載請註明出處!

相關文章