【引言】(完整程式碼在最後面)
本文將透過一個簡單的計數器應用案例,介紹如何利用鴻蒙NEXT的特性開發高效、美觀的應用程式。我們將涵蓋計數器的基本功能實現、使用者介面設計、資料持久化及動畫效果的新增。
【環境準備】
電腦系統:windows 10
開發工具:DevEco Studio 5.0.1 Beta3 Build Version: 5.0.5.200
工程版本:API 13
真機:Mate60 Pro
語言:ArkTS、ArkUI
【專案概述】
本專案旨在建立一個多計數器應用,使用者可以自由地新增、編輯、重置和刪除計數器。每個計數器具有獨立的名稱、當前值、增加步長和減少步長。應用還包括總計數的顯示,以便使用者快速瞭解所有計數器的總和。
【功能實現】
1、計數器模型
首先,我們定義了一個CounterItem類來表示單個計數器,其中包含了計數器的基本屬性和行為。
@ObservedV2 class CounterItem { id: number = ++Index.counterId; @Trace name: string; @Trace count: number = 0; @Trace scale: ScaleOptions = { x: 1, y: 1 }; upStep: number = 1; downStep: number = 1; constructor(name: string) { this.name = name; } }
2、應用入口與狀態管理
應用的主入口元件Index負責管理計數器列表、總計數、以及UI的狀態。這裡使用了@State和@Watch裝飾器來監控狀態的變化。
@Entry @Component struct Index { static counterStorageKey: string = "counterStorageKey"; static counterId: number = 0; @State listSpacing: number = 20; @State listItemHeight: number = 120; @State baseFontSize: number = 60; @State @Watch('updateTotalCount') counters: CounterItem[] = []; @State totalCount: number = 0; @State isSheetVisible: boolean = false; @State selectedIndex: number = 0; // ...其他方法 }
3、資料持久化
為了保證資料在應用重啟後仍然可用,我們使用了preferences模組來同步地讀取和寫入資料。
saveDataToLocal() { const saveData: object[] = this.counters.map(counter => ({ count: counter.count, name: counter.name, upStep: counter.upStep, downStep: counter.downStep, })); this.dataPreferences?.putSync(Index.counterStorageKey, JSON.stringify(saveData)); this.dataPreferences?.flush(); }
4、使用者介面
使用者介面的設計採用了現代簡潔的風格,主要由頂部的總計數顯示區、中間的計數器列表區和底部的操作按鈕組成。列表項支援左右滑動以顯示重置和刪除按鈕。
@Builder itemStart(index: number) { Row() { Text('重置').fontColor(Color.White).fontSize('40lpx').textAlign(TextAlign.Center).width('180lpx'); } .height('100%') .backgroundColor(Color.Orange) .justifyContent(FlexAlign.SpaceEvenly) .borderRadius({ topLeft: 10, bottomLeft: 10 }) .onClick(() => { this.counters[index].count = 0; this.updateTotalCount(); this.listScroller.closeAllSwipeActions(); }); }
5、動畫效果
當使用者新增新的計數器時,透過動畫效果讓新計數器逐漸放大至正常尺寸,提升使用者體驗。
this.counters.unshift(new CounterItem(`新計數項${Index.counterId}`)); this.listScroller.scrollTo({ xOffset: 0, yOffset: 0 }); this.counters[0].scale = { x: 0.8, y: 0.8 }; animateTo({ duration: 1000, curve: curves.springCurve(0, 10, 80, 10), iterations: 1, onFinish: () => {} }, () => { this.counters[0].scale = { x: 1, y: 1 }; });
【總結】
透過上述步驟,我們成功地構建了一個具備基本功能的計數器應用。在這個過程中,我們不僅學習瞭如何使用鴻蒙NEXT提供的各種API,還掌握瞭如何結合動畫、資料持久化等技術點來最佳化使用者體驗。希望本文能為你的鴻蒙開發之旅提供一些幫助和靈感!
【完整程式碼】
import { curves, promptAction } from '@kit.ArkUI' // 匯入動畫曲線和提示操作 import { preferences } from '@kit.ArkData' // 匯入偏好設定模組 @ObservedV2 // 觀察者裝飾器,監控狀態變化 class CounterItem { id: number = ++Index.counterId // 計數器ID,自動遞增 @Trace name: string // 計數器名稱 @Trace count: number = 0 // 計數器當前值,初始為0 @Trace scale: ScaleOptions = { x: 1, y: 1 } // 計數器縮放比例,初始為1 upStep: number = 1 // 增加步長,初始為1 downStep: number = 1 // 減少步長,初始為1 constructor(name: string) { // 建構函式,初始化計數器名稱 this.name = name } } @Entry // 入口元件裝飾器 @Component // 元件裝飾器 struct Index { static counterStorageKey: string = "counterStorageKey" // 儲存計數器資料的鍵 static counterId: number = 0 // 靜態計數器ID @State listSpacing: number = 20 // 列表項間距 @State listItemHeight: number = 120 // 列表項高度 @State baseFontSize: number = 60 // 基礎字型大小 @State @Watch('updateTotalCount') counters: CounterItem[] = [] // 計數器陣列,監控總計數更新 @State totalCount: number = 0 // 總計數 @State isSheetVisible: boolean = false // 控制底部彈出表單的可見性 @State selectedIndex: number = 0 // 當前選中的計數器索引 listScroller: ListScroller = new ListScroller() // 列表滾動器例項 dataPreferences: preferences.Preferences | undefined = undefined // 偏好設定例項 updateTotalCount() { // 更新總計數的方法 let total = 0; // 初始化總計數 for (let i = 0; i < this.counters.length; i++) { // 遍歷計數器陣列 total += this.counters[i].count // 累加每個計數器的count值 } this.totalCount = total // 更新總計數 this.saveDataToLocal() // 儲存資料到本地 } saveDataToLocal() { // 儲存計數器資料到本地的方法 const saveData: object[] = [] // 初始化儲存資料的陣列 for (let i = 0; i < this.counters.length; i++) { // 遍歷計數器陣列 let counter: CounterItem = this.counters[i] // 獲取當前計數器 saveData.push(Object({ // 將計數器資料新增到儲存陣列 count: counter.count, name: counter.name, upStep: counter.upStep, downStep: counter.downStep, })) } this.dataPreferences?.putSync(Index.counterStorageKey, JSON.stringify(saveData)) // 將資料儲存到偏好設定 this.dataPreferences?.flush() // 重新整理偏好設定 } @Builder // 構建器裝飾器 itemStart(index: number) { // 列表項左側的重置按鈕 Row() { Text('重置').fontColor(Color.White).fontSize('40lpx')// 顯示“重置”文字 .textAlign(TextAlign.Center)// 文字居中 .width('180lpx') // 設定寬度 } .height('100%') // 設定高度 .backgroundColor(Color.Orange) // 設定背景顏色 .justifyContent(FlexAlign.SpaceEvenly) // 設定內容均勻分佈 .borderRadius({ topLeft: 10, bottomLeft: 10 }) // 設定圓角 .onClick(() => { // 點選事件 this.counters[index].count = 0 // 重置計數器的count為0 this.updateTotalCount() // 更新總計數 this.listScroller.closeAllSwipeActions() // 關閉所有滑動操作 }) } @Builder // 構建器裝飾器 itemEnd(index: number) { // 列表項右側的刪除按鈕 Row() { Text('刪除').fontColor(Color.White).fontSize('40lpx')// 顯示“刪除”文字 .textAlign(TextAlign.Center)// 文字居中 .width('180lpx') // 設定寬度 } .height('100%') // 設定高度 .backgroundColor(Color.Red) // 設定背景顏色 .justifyContent(FlexAlign.SpaceEvenly) // 設定內容均勻分佈 .borderRadius({ topRight: 10, bottomRight: 10 }) // 設定圓角 .onClick(() => { // 點選事件 this.counters.splice(index, 1) // 從陣列中刪除計數器 this.listScroller.closeAllSwipeActions() // 關閉所有滑動操作 promptAction.showToast({ // 顯示刪除成功的提示 message: '刪除成功', duration: 2000, bottom: '400lpx' }); }) } aboutToAppear(): void { // 元件即將出現時呼叫 const options: preferences.Options = { name: Index.counterStorageKey }; // 獲取偏好設定選項 this.dataPreferences = preferences.getPreferencesSync(getContext(), options); // 同步獲取偏好設定 const savedData: string = this.dataPreferences.getSync(Index.counterStorageKey, "[]") as string // 獲取儲存的資料 const parsedData: Array<CounterItem> = JSON.parse(savedData) as Array<CounterItem> // 解析資料 console.info(`parsedData:${JSON.stringify(parsedData)}`) // 列印解析後的資料 for (const item of parsedData) { // 遍歷解析後的資料 const newItem = new CounterItem(item.name) // 建立新的計數器例項 newItem.count = item.count // 設定計數器的count newItem.upStep = item.upStep // 設定計數器的upStep newItem.downStep = item.downStep // 設定計數器的downStep this.counters.push(newItem) // 將新計數器新增到陣列 } this.updateTotalCount() // 更新總計數 } build() { // 構建元件的UI Column() { Text('計數器')// 顯示標題 .width('100%')// 設定寬度 .height('88lpx')// 設定高度 .fontSize('38lpx')// 設定字型大小 .backgroundColor(Color.White)// 設定背景顏色 .textAlign(TextAlign.Center) // 文字居中 Column() { List({ space: this.listSpacing, scroller: this.listScroller }) { // 建立列表 ForEach(this.counters, (counter: CounterItem, index: number) => { // 遍歷計數器陣列 ListItem() { // 列表項 Row() { // 行佈局 Stack() { // 堆疊佈局 Rect().fill("#65DACC").width(`${this.baseFontSize / 2}lpx`).height('4lpx') // 上方橫條 Circle()// 圓形按鈕 .width(`${this.baseFontSize}lpx`) .height(`${this.baseFontSize}lpx`) .fillOpacity(0)// 透明填充 .borderWidth('4lpx')// 邊框寬度 .borderRadius('50%')// 圓角 .borderColor("#65DACC") // 邊框顏色 } .width(`${this.baseFontSize * 2}lpx`) // 設定寬度 .height(`100%`) // 設定高度 .clickEffect({ scale: 0.6, level: ClickEffectLevel.LIGHT }) // 點選效果 .onClick(() => { // 點選事件 counter.count -= counter.downStep // 減少計數器的count this.updateTotalCount() // 更新總計數 }) Stack() { // 堆疊佈局 Text(counter.name)// 顯示計數器名稱 .fontSize(`${this.baseFontSize / 2}lpx`)// 設定字型大小 .fontColor(Color.Gray)// 設定字型顏色 .margin({ bottom: `${this.baseFontSize * 2}lpx` }) // 設定底部邊距 Text(`${counter.count}`)// 顯示計數器當前值 .fontColor(Color.Black)// 設定字型顏色 .fontSize(`${this.baseFontSize}lpx`) // 設定字型大小 }.height('100%') // 設定高度 Stack() { // 堆疊佈局 Rect().fill("#65DACC").width(`${this.baseFontSize / 2}lpx`).height('4lpx') // 下方橫條 Rect() .fill("#65DACC") .width(`${this.baseFontSize / 2}lpx`) .height('4lpx') .rotate({ angle: 90 }) // 垂直橫條 Circle()// 圓形按鈕 .width(`${this.baseFontSize}lpx`)// 設定寬度 .height(`${this.baseFontSize}lpx`)// 設定高度 .fillOpacity(0)// 透明填充 .borderWidth('4lpx')// 邊框寬度 .borderRadius('50%')// 圓角 .borderColor("#65DACC") // 邊框顏色 } .width(`${this.baseFontSize * 2}lpx`) // 設定堆疊佈局寬度 .height(`100%`) // 設定堆疊佈局高度 .clickEffect({ scale: 0.6, level: ClickEffectLevel.LIGHT }) // 點選效果 .onClick(() => { // 點選事件 counter.count += counter.upStep // 增加計數器的count this.updateTotalCount() // 更新總計數 }) } .width('100%') // 設定列表項寬度 .backgroundColor(Color.White) // 設定背景顏色 .justifyContent(FlexAlign.SpaceBetween) // 設定內容兩端對齊 .padding({ left: '30lpx', right: '30lpx' }) // 設定左右內邊距 } .height(this.listItemHeight) // 設定列表項高度 .width('100%') // 設定列表項寬度 .margin({ // 設定列表項的外邊距 top: index == 0 ? this.listSpacing : 0, // 如果是第一個項,設定上邊距 bottom: index == this.counters.length - 1 ? this.listSpacing : 0 // 如果是最後一個項,設定下邊距 }) .borderRadius(10) // 設定圓角 .clip(true) // 裁剪超出部分 .swipeAction({ start: this.itemStart(index), end: this.itemEnd(index) }) // 設定滑動操作 .scale(counter.scale) // 設定計數器縮放比例 .onClick(() => { // 點選事件 this.selectedIndex = index // 設定當前選中的計數器索引 this.isSheetVisible = true // 顯示底部彈出表單 }) }, (counter: CounterItem) => counter.id.toString())// 使用計數器ID作為唯一鍵 .onMove((from: number, to: number) => { // 列表項移動事件 const tmp = this.counters.splice(from, 1); // 從原位置移除計數器 this.counters.splice(to, 0, tmp[0]) // 插入到新位置 }) } .scrollBar(BarState.Off) // 隱藏捲軸 .width('648lpx') // 設定列表寬度 .height('100%') // 設定列表高度 } .width('100%') // 設定列寬度 .layoutWeight(1) // 設定佈局權重 Row() { // 底部合計行 Column() { // 列布局 Text('合計').fontSize('26lpx').fontColor(Color.Gray) // 顯示“合計”文字 Text(`${this.totalCount}`).fontSize('38lpx').fontColor(Color.Black) // 顯示總計數 }.margin({ left: '50lpx' }) // 設定左邊距 .justifyContent(FlexAlign.Start) // 設定內容左對齊 .alignItems(HorizontalAlign.Start) // 設定專案左對齊 .width('300lpx') // 設定列寬度 Row() { // 新增按鈕行 Text('新增').fontColor(Color.White).fontSize('28lpx') // 顯示“新增”文字 } .onClick(() => { // 點選事件 this.counters.unshift(new CounterItem(`新計數項${Index.counterId}`)) // 新增新計數器 this.listScroller.scrollTo({ xOffset: 0, yOffset: 0 }) // 滾動到頂部 this.counters[0].scale = { x: 0.8, y: 0.8 }; // 設定新計數器縮放 animateTo({ // 動畫效果 duration: 1000, // 動畫持續時間 curve: curves.springCurve(0, 10, 80, 10), // 動畫曲線 iterations: 1, // 動畫迭代次數 onFinish: () => { // 動畫完成後的回撥 } }, () => { this.counters[0].scale = { x: 1, y: 1 }; // 恢復縮放 }) }) .width('316lpx') // 設定按鈕寬度 .height('88lpx') // 設定按鈕高度 .backgroundColor("#65DACC") // 設定按鈕背景顏色 .borderRadius(10) // 設定按鈕圓角 .justifyContent(FlexAlign.Center) // 設定內容居中 }.width('100%').height('192lpx').backgroundColor(Color.White) // 設定行寬度和高度 } .backgroundColor("#f2f2f7") // 設定背景顏色 .width('100%') // 設定寬度 .height('100%') // 設定高度 .bindSheet(this.isSheetVisible, this.mySheet(), { // 繫結底部彈出表單 height: 300, // 設定表單高度 dragBar: false, // 禁用拖動條 onDisappear: () => { // 表單消失時的回撥 this.isSheetVisible = false // 隱藏表單 } }) } @Builder // 構建器裝飾器 mySheet() { // 建立底部彈出表單 Column({ space: 20 }) { // 列布局,設定間距 Row() { // 行佈局 Text('計數標題:') // 顯示“計數標題”文字 TextInput({ text: this.counters[this.selectedIndex].name }).width('300lpx').onChange((value) => { // 輸入框,繫結計數器名稱 this.counters[this.selectedIndex].name = value // 更新計數器名稱 }) } Row() { // 行佈局 Text('增加步長:') // 顯示“增加步長”文字 TextInput({ text: `${this.counters[this.selectedIndex].upStep}` })// 輸入框,繫結增加步長 .width('300lpx')// 設定輸入框寬度 .type(InputType.Number)// 設定輸入框型別為數字 .onChange((value) => { // 輸入框變化事件 this.counters[this.selectedIndex].upStep = parseInt(value) // 更新增加步長 this.updateTotalCount() // 更新總計數 }) } Row() { // 行佈局 Text('減少步長:') // 顯示“減少步長”文字 TextInput({ text: `${this.counters[this.selectedIndex].downStep}` })// 輸入框,繫結減少步長 .width('300lpx')// 設定輸入框寬度 .type(InputType.Number)// 設定輸入框型別為數字 .onChange((value) => { // 輸入框變化事件 this.counters[this.selectedIndex].downStep = parseInt(value) // 更新減少步長 this.updateTotalCount() // 更新總計數 }) } } .justifyContent(FlexAlign.Start) // 設定內容左對齊 .padding(40) // 設定內邊距 .width('100%') // 設定寬度 .height('100%') // 設定高度 .backgroundColor(Color.White) // 設定背景顏色 } }