[HarmonyOS NEXT 實(shí)戰(zhàn)案例:健康應(yīng)用] 高級(jí)篇 - 健康數(shù)據(jù)儀表盤的高級(jí)布局與自適應(yīng)設(shè)計(jì)
項(xiàng)目已開源,開源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 歡迎fork & star效果演示
引言
在前兩篇教程中,我們學(xué)習(xí)了如何使用HarmonyOS NEXT的RowSplit組件構(gòu)建健康數(shù)據(jù)儀表盤的基本布局,以及如何添加交互功能和狀態(tài)管理。本篇教程將進(jìn)一步深入,講解健康數(shù)據(jù)儀表盤的高級(jí)布局技巧和自適應(yīng)設(shè)計(jì),使應(yīng)用能夠在不同尺寸的設(shè)備上提供一致且優(yōu)質(zhì)的用戶體驗(yàn)。
自適應(yīng)布局概述
自適應(yīng)布局是指應(yīng)用界面能夠根據(jù)設(shè)備屏幕尺寸和方向自動(dòng)調(diào)整布局,提供最佳的用戶體驗(yàn)。在HarmonyOS NEXT中,我們可以使用以下技術(shù)實(shí)現(xiàn)自適應(yīng)布局:
技術(shù)描述使用場(chǎng)景媒體查詢根據(jù)設(shè)備屏幕尺寸和方向應(yīng)用不同的樣式在不同尺寸的設(shè)備上使用不同的布局百分比布局使用百分比值設(shè)置組件尺寸使組件尺寸相對(duì)于父容器自動(dòng)調(diào)整彈性布局使用彈性布局使組件自動(dòng)填充可用空間使組件尺寸相對(duì)于可用空間自動(dòng)調(diào)整柵格布局使用柵格系統(tǒng)組織界面元素創(chuàng)建復(fù)雜且響應(yīng)式的布局高級(jí)布局技巧
1. 使用媒體查詢實(shí)現(xiàn)響應(yīng)式布局媒體查詢?cè)试S我們根據(jù)設(shè)備屏幕尺寸和方向應(yīng)用不同的樣式。在HarmonyOS NEXT中,我們可以使用@MediaQuery裝飾器實(shí)現(xiàn)媒體查詢:
@Component export struct HealthDashboard { @State currentTab: string = 'steps' @State datalist: DataItem[] = [...] @State Status: StatusItem[] = [...] // 添加媒體查詢狀態(tài) @State isWideScreen: boolean = false @State navWidth: string = '25%' @State contentPadding: number = 20 // 媒體查詢裝飾器 @MediaQuery(MediaQueryCondition.WIDE_SCREEN) onWideScreen(matches: boolean) { this.isWideScreen = matches this.navWidth = matches ? '20%' : '25%' this.contentPadding = matches ? 30 : 20 } build() { // 根據(jù)屏幕寬度選擇不同的布局 if (this.isWideScreen) { this.buildWideScreenLayout() } else { this.buildNormalLayout() } } // 寬屏布局 buildWideScreenLayout() { RowSplit() { // 左側(cè)導(dǎo)航 Column() { Text('健康數(shù)據(jù)') .fontSize(24) // 增大字體 .fontWeight(FontWeight.Bold) .margin({ top: 30, bottom: 40 }) // 增大邊距 ForEach(this.datalist, (item: DataItem) => { Button(item.name) .width('80%') .height(60) // 增大按鈕高度 .fontSize(18) // 增大字體 .backgroundColor(this.currentTab === item.id ? '#4CAF50' : 'transparent') .fontColor(this.currentTab === item.id ? '#ffffff' : '#333333') .onClick(() => { this.currentTab = item.id }) .margin({ bottom: 15 }) // 增大邊距 }) } .width(this.navWidth) .backgroundColor('#f5f9f5') // 主內(nèi)容區(qū) Column() { // 根據(jù)當(dāng)前標(biāo)簽頁顯示不同內(nèi)容 if (this.currentTab === 'steps') { this.buildStepsContent() } else if (this.currentTab === 'heart') { this.buildHeartContent() } else { this.buildSleepContent() } } .padding(this.contentPadding) } .height(500) // 增大高度 } // 普通布局 buildNormalLayout() { RowSplit() { // 左側(cè)導(dǎo)航 Column() { Text('健康數(shù)據(jù)') .fontSize(20) .fontWeight(FontWeight.Bold) .margin({ top: 20, bottom: 30 }) ForEach(this.datalist, (item: DataItem) => { Button(item.name) .width('80%') .height(50) .fontSize(16) .backgroundColor(this.currentTab === item.id ? '#4CAF50' : 'transparent') .fontColor(this.currentTab === item.id ? '#ffffff' : '#333333') .onClick(() => { this.currentTab = item.id }) .margin({ bottom: 10 }) }) } .width(this.navWidth) .backgroundColor('#f5f9f5') // 主內(nèi)容區(qū) Column() { // 根據(jù)當(dāng)前標(biāo)簽頁顯示不同內(nèi)容 if (this.currentTab === 'steps') { this.buildStepsContent() } else if (this.currentTab === 'heart') { this.buildHeartContent() } else { this.buildSleepContent() } } .padding(this.contentPadding) } .height(400) } // 步數(shù)內(nèi)容構(gòu)建方法 buildStepsContent() { Column() { Text('今日步數(shù)') .fontSize(this.isWideScreen ? 22 : 18) .margin({ bottom: this.isWideScreen ? 30 : 20 }) Progress({ value: 7500, total: 10000, type: ProgressType.Linear }) .width('90%') .height(this.isWideScreen ? 25 : 20) Text('7500/10000 步') .fontSize(this.isWideScreen ? 18 : 16) .margin({ top: this.isWideScreen ? 15 : 10 }) } .height('100%') .justifyContent(FlexAlign.Center) } // 心率內(nèi)容構(gòu)建方法 buildHeartContent() { Column() { Text('心率監(jiān)測(cè)') .fontSize(this.isWideScreen ? 22 : 18) .margin({ bottom: this.isWideScreen ? 30 : 20 }) Image($r('app.media.big20')) .width('90%') .height(this.isWideScreen ? 250 : 200) Text('平均心率: 72 bpm') .fontSize(this.isWideScreen ? 18 : 16) .margin({ top: this.isWideScreen ? 25 : 20 }) } .height('100%') .justifyContent(FlexAlign.Center) } // 睡眠內(nèi)容構(gòu)建方法 buildSleepContent() { Column() { Text('睡眠分析') .fontSize(this.isWideScreen ? 22 : 18) .margin({ bottom: this.isWideScreen ? 30 : 20 }) // 在寬屏模式下使用水平布局,在普通模式下使用垂直布局 if (this.isWideScreen) { Row() { // 睡眠狀態(tài)圖表 this.buildSleepChart() // 睡眠狀態(tài)圖例 this.buildSleepLegend() } .width('100%') .justifyContent(FlexAlign.SpaceBetween) } else { Column() { // 睡眠狀態(tài)圖表 this.buildSleepChart() // 睡眠狀態(tài)圖例 this.buildSleepLegend() .margin({ top: 20 }) } .width('100%') } } .height('100%') .justifyContent(FlexAlign.Center) } // 睡眠圖表構(gòu)建方法 @Builder buildSleepChart() { Stack() { // 使用餅圖展示睡眠狀態(tài) Row() { ForEach(this.Status, (item: StatusItem) => { Column() { Stack() { Circle() .width(this.isWideScreen ? 150 : 120) .height(this.isWideScreen ? 150 : 120) .fill('#f0f0f0') Arc() .width(this.isWideScreen ? 150 : 120) .height(this.isWideScreen ? 150 : 120) .startAngle(0) .sweepAngle(item.value * 3.6) // 將百分比轉(zhuǎn)換為角度 .stroke(item.color) .strokeWidth(this.isWideScreen ? 20 : 15) } } }) } } .width(this.isWideScreen ? '50%' : '100%') } // 睡眠圖例構(gòu)建方法 @Builder buildSleepLegend() { Column() { ForEach(this.Status, (item: StatusItem) => { Row() { Circle() .width(this.isWideScreen ? 20 : 15) .height(this.isWideScreen ? 20 : 15) .fill(item.color) .margin({ right: this.isWideScreen ? 15 : 10 }) Text(`${item.label}: ${item.value}%`) .fontSize(this.isWideScreen ? 16 : 14) } .margin({ bottom: this.isWideScreen ? 10 : 5 }) }) } .width(this.isWideScreen ? '40%' : '100%') } }2. 使用柵格布局組織復(fù)雜界面
柵格布局是一種將界面劃分為網(wǎng)格的布局方式,可以幫助我們創(chuàng)建復(fù)雜且響應(yīng)式的布局。在HarmonyOS NEXT中,我們可以使用GridRow和GridCol組件實(shí)現(xiàn)柵格布局:
@Component struct GridLayoutExample { build() { Column() { GridRow() { GridCol({ span: 12 }) { Text('健康數(shù)據(jù)儀表盤') .fontSize(24) .fontWeight(FontWeight.Bold) .width('100%') .textAlign(TextAlign.Center) .margin({ bottom: 20 }) } GridCol({ span: 4 }) { this.buildNavigation() } GridCol({ span: 8 }) { this.buildContent() } } .gutter(16) // 設(shè)置列間距 } .width('100%') .padding(20) } @Builder buildNavigation() { // 導(dǎo)航內(nèi)容 } @Builder buildContent() { // 主內(nèi)容 } }3. 使用@BuilderParam實(shí)現(xiàn)布局定制
@BuilderParam裝飾器允許我們將UI構(gòu)建邏輯作為參數(shù)傳遞給組件,從而實(shí)現(xiàn)高度定制化的布局:
@Component struct DashboardCard { @Prop title: string @BuilderParam content: () => void @BuilderParam header?: () => void @BuilderParam footer?: () => void build() { Column() { // 卡片頭部(可選) if (this.header) { this.header() } else { Text(this.title) .fontSize(18) .fontWeight(FontWeight.Bold) .margin({ bottom: 16 }) } // 卡片內(nèi)容 this.content() // 卡片底部(可選) if (this.footer) { this.footer() } } .width('100%') .padding(16) .backgroundColor('#ffffff') .borderRadius(8) .shadow({ radius: 4, color: '#0000001A', offsetX: 0, offsetY: 2 }) } }
使用這個(gè)組件可以創(chuàng)建高度定制化的儀表盤卡片:
DashboardCard({ title: '今日步數(shù)', header: () => { Row() { Text('今日步數(shù)') .fontSize(18) .fontWeight(FontWeight.Bold) Blank() Button('查看詳情') .fontSize(14) .height(32) .backgroundColor('#4CAF50') } .width('100%') .margin({ bottom: 16 }) }, content: () => { Column() { Progress({ value: 7500, total: 10000, type: ProgressType.Linear }) .width('100%') .height(20) Text('7500/10000 步') .fontSize(16) .margin({ top: 10 }) } }, footer: () => { Text('目標(biāo)完成率: 75%') .fontSize(14) .fontColor('#666666') .margin({ top: 16 }) } })
高級(jí)組件封裝
1. 數(shù)據(jù)儀表盤組件我們可以創(chuàng)建一個(gè)通用的數(shù)據(jù)儀表盤組件,用于展示各種類型的健康數(shù)據(jù):
@Component struct DataDashboard { @Prop title: string @Prop value: number @Prop unit: string @Prop maxValue: number @Prop color: string = '#4CAF50' @Prop type: string = 'circle' // 'circle' | 'linear' | 'arc' build() { Column() { Text(this.title) .fontSize(16) .fontWeight(FontWeight.Medium) .margin({ bottom: 16 }) if (this.type === 'circle') { this.buildCircleProgress() } else if (this.type === 'linear') { this.buildLinearProgress() } else { this.buildArcProgress() } Text(`${this.value} ${this.unit}`) .fontSize(18) .fontWeight(FontWeight.Bold) .margin({ top: 16 }) } .width('100%') .padding(16) .backgroundColor('#ffffff') .borderRadius(8) .shadow({ radius: 4, color: '#0000001A', offsetX: 0, offsetY: 2 }) } @Builder buildCircleProgress() { Stack() { Circle() .width(120) .height(120) .fill('#f0f0f0') Progress({ value: this.value, total: this.maxValue, type: ProgressType.Ring }) .width(120) .height(120) .color(this.color) } } @Builder buildLinearProgress() { Progress({ value: this.value, total: this.maxValue, type: ProgressType.Linear }) .width('100%') .height(20) .color(this.color) } @Builder buildArcProgress() { Stack() { Arc() .width(120) .height(120) .startAngle(180) .sweepAngle(180) .stroke('#f0f0f0') .strokeWidth(10) Arc() .width(120) .height(120) .startAngle(180) .sweepAngle(180 * this.value / this.maxValue) .stroke(this.color) .strokeWidth(10) } } }
使用這個(gè)組件可以創(chuàng)建各種類型的數(shù)據(jù)儀表盤:
Row() { DataDashboard({ title: '今日步數(shù)', value: 7500, unit: '步', maxValue: 10000, color: '#4CAF50', type: 'linear' }) .layoutWeight(1) .margin({ right: 8 }) DataDashboard({ title: '平均心率', value: 72, unit: 'bpm', maxValue: 100, color: '#F44336', type: 'circle' }) .layoutWeight(1) .margin({ left: 8 }) } .width('100%') .margin({ bottom: 16 }) Row() { DataDashboard({ title: '睡眠時(shí)長', value: 7.5, unit: '小時(shí)', maxValue: 10, color: '#2196F3', type: 'arc' }) .layoutWeight(1) .margin({ right: 8 }) DataDashboard({ title: '卡路里', value: 350, unit: 'kcal', maxValue: 500, color: '#FF9800', type: 'circle' }) .layoutWeight(1) .margin({ left: 8 }) } .width('100%')2. 圖表組件
我們可以創(chuàng)建一個(gè)通用的圖表組件,用于展示各種類型的健康數(shù)據(jù)趨勢(shì):
@Component struct ChartComponent { @Prop title: string @Prop data: Array<{ label: string, value: number }> @Prop color: string = '#4CAF50' @Prop type: string = 'bar' // 'bar' | 'line' | 'pie' @State private maxValue: number = 0 @State private chartWidth: number = 0 @State private chartHeight: number = 200 aboutToAppear() { // 計(jì)算最大值 this.maxValue = Math.max(...this.data.map(item => item.value)) } build() { Column() { Text(this.title) .fontSize(16) .fontWeight(FontWeight.Medium) .margin({ bottom: 16 }) if (this.type === 'bar') { this.buildBarChart() } else if (this.type === 'line') { this.buildLineChart() } else { this.buildPieChart() } } .width('100%') .padding(16) .backgroundColor('#ffffff') .borderRadius(8) .shadow({ radius: 4, color: '#0000001A', offsetX: 0, offsetY: 2 }) .onAreaChange((oldArea: Area, newArea: Area) => { this.chartWidth = newArea.width as number - 32 // 減去內(nèi)邊距 }) } @Builder buildBarChart() { Column() { // Y軸最大值 Text(`${this.maxValue}`) .fontSize(12) .fontColor('#666666') .textAlign(TextAlign.End) .width('100%') // 圖表區(qū)域 Row() { ForEach(this.data, (item, index) => { Column() { // 柱形 Stack() { Rectangle() .width(20) .height(this.chartHeight) .fill('#f0f0f0') Rectangle() .width(20) .height(this.chartHeight * item.value / this.maxValue) .fill(this.color) .alignSelf(ItemAlign.End) } .height(this.chartHeight) // 標(biāo)簽 Text(item.label) .fontSize(12) .fontColor('#666666') .margin({ top: 8 }) } .margin({ right: index < this.data.length - 1 ? (this.chartWidth - 20 * this.data.length) / (this.data.length - 1) : 0 }) }) } .width('100%') .justifyContent(FlexAlign.SpaceBetween) // X軸 Divider() .width('100%') .color('#f0f0f0') .margin({ top: 8 }) } } @Builder buildLineChart() { // 線形圖實(shí)現(xiàn) // 由于HarmonyOS NEXT目前沒有直接的線形圖組件,這里可以使用Path組件繪制折線 // 或者使用第三方圖表庫 } @Builder buildPieChart() { // 餅圖實(shí)現(xiàn) // 可以使用多個(gè)Arc組件組合實(shí)現(xiàn)餅圖 // 或者使用第三方圖表庫 } }
使用這個(gè)組件可以創(chuàng)建各種類型的圖表:
ChartComponent({ title: '一周步數(shù)趨勢(shì)', data: [ { label: '周一', value: 8000 }, { label: '周二', value: 7500 }, { label: '周三', value: 9000 }, { label: '周四', value: 8500 }, { label: '周五', value: 7000 }, { label: '周六', value: 6500 }, { label: '周日', value: 8000 } ], color: '#4CAF50', type: 'bar' }) .margin({ bottom: 16 })
主題切換
我們可以實(shí)現(xiàn)主題切換功能,允許用戶在淺色主題和深色主題之間切換:
@Component export struct HealthDashboard { @State currentTab: string = 'steps' @State datalist: DataItem[] = [...] @State Status: StatusItem[] = [...] // 添加主題狀態(tài) @State isDarkMode: boolean = false @State themeColors: { [key: string]: string } = { // 淺色主題顏色 light: { background: '#ffffff', navBackground: '#f5f9f5', text: '#333333', secondaryText: '#666666', primary: '#4CAF50', secondary: '#8BC34A', tertiary: '#CDDC39', cardBackground: '#ffffff', divider: '#f0f0f0' }, // 深色主題顏色 dark: { background: '#121212', navBackground: '#1e1e1e', text: '#ffffff', secondaryText: '#aaaaaa', primary: '#81C784', secondary: '#AED581', tertiary: '#DCE775', cardBackground: '#1e1e1e', divider: '#333333' } } // 獲取當(dāng)前主題顏色 get colors() { return this.isDarkMode ? this.themeColors.dark : this.themeColors.light } build() { Column() { // 主題切換按鈕 Row() { Text('主題模式') .fontSize(16) .fontColor(this.colors.text) Blank() Toggle({ type: ToggleType.Switch, isOn: this.isDarkMode }) .onChange((isOn: boolean) => { this.isDarkMode = isOn }) } .width('100%') .padding(16) // 健康數(shù)據(jù)儀表盤 RowSplit() { // 左側(cè)導(dǎo)航 Column() { Text('健康數(shù)據(jù)') .fontSize(20) .fontWeight(FontWeight.Bold) .fontColor(this.colors.text) .margin({ top: 20, bottom: 30 }) ForEach(this.datalist, (item: DataItem) => { Button(item.name) .width('80%') .height(50) .fontSize(16) .backgroundColor(this.currentTab === item.id ? this.colors.primary : 'transparent') .fontColor(this.currentTab === item.id ? '#ffffff' : this.colors.text) .onClick(() => { this.currentTab = item.id }) .margin({ bottom: 10 }) }) } .width('25%') .backgroundColor(this.colors.navBackground) // 主內(nèi)容區(qū) Column() { // 根據(jù)當(dāng)前標(biāo)簽頁顯示不同內(nèi)容 if (this.currentTab === 'steps') { this.buildStepsContent() } else if (this.currentTab === 'heart') { this.buildHeartContent() } else { this.buildSleepContent() } } .padding(20) .backgroundColor(this.colors.background) } .height(400) } .width('100%') .backgroundColor(this.colors.background) } // 步數(shù)內(nèi)容構(gòu)建方法 buildStepsContent() { Column() { Text('今日步數(shù)') .fontSize(18) .fontColor(this.colors.text) .margin({ bottom: 20 }) Progress({ value: 7500, total: 10000, type: ProgressType.Linear }) .width('90%') .height(20) .color(this.colors.primary) Text('7500/10000 步') .fontSize(16) .fontColor(this.colors.text) .margin({ top: 10 }) } .height('100%') .justifyContent(FlexAlign.Center) } // 心率內(nèi)容構(gòu)建方法 buildHeartContent() { // 類似地,更新心率內(nèi)容的顏色 } // 睡眠內(nèi)容構(gòu)建方法 buildSleepContent() { Column() { Text('睡眠分析') .fontSize(18) .fontColor(this.colors.text) .margin({ bottom: 20 }) Stack() { ForEach(this.Status, (item: StatusItem, index) => { Row() { Circle() .width(15) .height(15) .fill(index === 0 ? this.colors.primary : (index === 1 ? this.colors.secondary : this.colors.tertiary)) .margin({ right: 10 }) Text(`${item.label}: ${item.value}%`) .fontSize(14) .fontColor(this.colors.text) } .margin({ bottom: 5 }) }) } } .height('100%') .justifyContent(FlexAlign.Center) } }
多語言支持
我們可以實(shí)現(xiàn)多語言支持,允許應(yīng)用在不同語言環(huán)境下顯示不同的文本:
@Component export struct HealthDashboard { @State currentTab: string = 'steps' @State datalist: DataItem[] = [...] @State Status: StatusItem[] = [...] // 添加語言狀態(tài) @State currentLanguage: string = 'zh' @State translations: { [key: string]: { [key: string]: string } } = { // 中文翻譯 zh: { title: '健康數(shù)據(jù)', steps: '步數(shù)', heart: '心率', sleep: '睡眠', todaySteps: '今日步數(shù)', stepsUnit: '步', heartRate: '心率監(jiān)測(cè)', averageHeartRate: '平均心率', bpm: 'bpm', sleepAnalysis: '睡眠分析', deepSleep: '深睡', lightSleep: '淺睡', awake: '清醒' }, // 英文翻譯 en: { title: 'Health Data', steps: 'Steps', heart: 'Heart Rate', sleep: 'Sleep', todaySteps: 'Today's Steps', stepsUnit: 'steps', heartRate: 'Heart Rate Monitor', averageHeartRate: 'Average Heart Rate', bpm: 'bpm', sleepAnalysis: 'Sleep Analysis', deepSleep: 'Deep Sleep', lightSleep: 'Light Sleep', awake: 'Awake' } } // 獲取當(dāng)前語言的翻譯 get t() { return this.translations[this.currentLanguage] } // 初始化數(shù)據(jù) aboutToAppear() { this.updateDatalist() this.updateStatus() } // 更新導(dǎo)航菜單數(shù)據(jù) updateDatalist() { this.datalist = [ { icon: $r('app.media.01'), name: this.t.steps, id: 'steps' }, { icon: $r('app.media.02'), name: this.t.heart, id: 'heart' }, { icon: $r('app.media.03'), name: this.t.sleep, id: 'sleep' } ] } // 更新睡眠狀態(tài)數(shù)據(jù) updateStatus() { this.Status = [ { value: 30, color: '#4CAF50', label: this.t.deepSleep }, { value: 50, color: '#8BC34A', label: this.t.lightSleep }, { value: 20, color: '#CDDC39', label: this.t.awake } ] } build() { Column() { // 語言切換按鈕 Row() { Text('Language / 語言') .fontSize(16) Blank() Button(this.currentLanguage === 'zh' ? 'English' : '中文') .onClick(() => { this.currentLanguage = this.currentLanguage === 'zh' ? 'en' : 'zh' this.updateDatalist() this.updateStatus() }) } .width('100%') .padding(16) // 健康數(shù)據(jù)儀表盤 RowSplit() { // 左側(cè)導(dǎo)航 Column() { Text(this.t.title) .fontSize(20) .fontWeight(FontWeight.Bold) .margin({ top: 20, bottom: 30 }) ForEach(this.datalist, (item: DataItem) => { Button(item.name) .width('80%') .height(50) .fontSize(16) .backgroundColor(this.currentTab === item.id ? '#4CAF50' : 'transparent') .fontColor(this.currentTab === item.id ? '#ffffff' : '#333333') .onClick(() => { this.currentTab = item.id }) .margin({ bottom: 10 }) }) } .width('25%') .backgroundColor('#f5f9f5') // 主內(nèi)容區(qū) Column() { // 根據(jù)當(dāng)前標(biāo)簽頁顯示不同內(nèi)容 if (this.currentTab === 'steps') { this.buildStepsContent() } else if (this.currentTab === 'heart') { this.buildHeartContent() } else { this.buildSleepContent() } } .padding(20) } .height(400) } .width('100%') } // 步數(shù)內(nèi)容構(gòu)建方法 buildStepsContent() { Column() { Text(this.t.todaySteps) .fontSize(18) .margin({ bottom: 20 }) Progress({ value: 7500, total: 10000, type: ProgressType.Linear }) .width('90%') .height(20) Text(`7500/10000 ${this.t.stepsUnit}`) .fontSize(16) .margin({ top: 10 }) } .height('100%') .justifyContent(FlexAlign.Center) } // 心率內(nèi)容構(gòu)建方法 buildHeartContent() { Column() { Text(this.t.heartRate) .fontSize(18) .margin({ bottom: 20 }) Image($r('app.media.big20')) .width('90%') .height(200) Text(`${this.t.averageHeartRate}: 72 ${this.t.bpm}`) .fontSize(16) .margin({ top: 20 }) } .height('100%') .justifyContent(FlexAlign.Center) } // 睡眠內(nèi)容構(gòu)建方法 buildSleepContent() { Column() { Text(this.t.sleepAnalysis) .fontSize(18) .margin({ bottom: 20 }) Stack() { ForEach(this.Status, (item: StatusItem) => { Row() { Circle() .width(15) .height(15) .fill(item.color) .margin({ right: 10 }) Text(`${item.label}: ${item.value}%`) .fontSize(14) } .margin({ bottom: 5 }) }) } } .height('100%') .justifyContent(FlexAlign.Center) } }
總結(jié)
在本教程中,我們學(xué)習(xí)了健康數(shù)據(jù)儀表盤的高級(jí)布局技巧和自適應(yīng)設(shè)計(jì),但案例仍有完善空間