HarmonyOS应用开发实战:萌宠日记 - 周月年
HarmonyOS应用开发实战:萌宠日记 - 周月年
前言
统计周期切换是数据统计页面的时间维度控制器,用户可以在周、月、年三种周期之间切换,查看不同时间范围内的数据统计。在萌宠日记的StatisticsPage中,周期切换使用胶囊按钮组设计,选中按钮使用品牌橙色背景 + 白色文字,未选中按钮保持透明背景 + 灰色文字,通过 @State 管理选中状态。
本文将从萌宠日记中统计周期切换的实现出发,深入解析胶囊按钮组设计、选中态管理、周期切换的数据过滤逻辑,以及不同周期下的数据聚合计算。
一、周期切换组件设计
1.1 核心实现
// StatisticsPage.ets — 周期切换 @State selectedPeriod: number = 0 private periods: string[] = ['周', '月', '年'] Row({ space: 0 }) { ForEach(this.periods, (period: string, index: number) => { Text(period) .fontSize(14) .fontColor(this.selectedPeriod === index ? '#FFFFFF' : '#666666') .fontWeight(this.selectedPeriod === index ? FontWeight.Bold : FontWeight.Normal) .padding({ left: 20, right: 20, top: 8, bottom: 8 }) .backgroundColor(this.selectedPeriod === index ? '#F5A623' : Color.Transparent) .borderRadius(16) .onClick(() => { this.selectedPeriod = index }) }) } .width('100%') .justifyContent(FlexAlign.Center) .padding({ top: 12 })1.2 按钮样式设计
| 状态 | 背景色 | 文字色 | 字重 | 视觉效果 |
|---|---|---|---|---|
| 选中 | #F5A623橙色 | #FFFFFF白色 | FontWeight.Bold | 突出高亮 |
| 未选中 | Color.Transparent透明 | #666666灰色 | FontWeight.Normal | 柔和低调 |
二、数据过滤逻辑
2.1 基础过滤
// 根据周期过滤数据 get filteredStats() { switch (this.selectedPeriod) { case 0: return this.getWeeklyStats() case 1: return this.getMonthlyStats() case 2: return this.getYearlyStats() } }2.2 各周期数据计算
// 周统计数据 getWeeklyStats(): StatsData { const now = new Date() const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000) const weeklyDiaries = this.allDiaries.filter(d => new Date(d.createdAt) >= weekAgo ) return { diaryCount: weeklyDiaries.length, moodDistribution: this.calculateMood(weeklyDiaries), activityStats: this.calculateActivity(weeklyDiaries) } } // 月统计数据 getMonthlyStats(): StatsData { const now = new Date() const monthAgo = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate()) const monthlyDiaries = this.allDiaries.filter(d => new Date(d.createdAt) >= monthAgo ) return { diaryCount: monthlyDiaries.length, moodDistribution: this.calculateMood(monthlyDiaries), activityStats: this.calculateActivity(monthlyDiaries) } } // 年统计数据 getYearlyStats(): StatsData { const now = new Date() const yearAgo = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate()) const yearlyDiaries = this.allDiaries.filter(d => new Date(d.createdAt) >= yearAgo ) return { diaryCount: yearlyDiaries.length, moodDistribution: this.calculateMood(yearlyDiaries), activityStats: this.calculateActivity(yearlyDiaries) } }三、周期与统计卡片的联动
3.1 统计卡片更新
// 日记数量卡片 — 根据周期显示不同数据 @Builder DiaryCountCard() { Column({ space: 8 }) { Text('日记数量').fontSize(16).fontWeight(FontWeight.Bold) Row() { Column({ space: 4 }) { Text(this.getPeriodLabel()).fontSize(13).fontColor('#999999') Row({ space: 4 }) { Text(`${this.filteredStats.diaryCount}`) .fontSize(28).fontWeight(FontWeight.Bold) Text('篇').fontSize(14).fontColor('#666666') } Text(this.getGrowthText()) .fontSize(12).fontColor('#4CAF50') } Blank() // 柱状图 this.WeeklyChart() } } } // 获取周期标签 getPeriodLabel(): string { const labels = ['本周记录', '本月记录', '本年记录'] return labels[this.selectedPeriod] }四、周期切换动画
4.1 内容过渡动画
@State cardOpacity: number = 1 onPeriodChange(index: number): void { // 淡出 animateTo({ duration: 150 }, () => { this.cardOpacity = 0 }) // 更新数据 this.selectedPeriod = index // 淡入 setTimeout(() => { animateTo({ duration: 150 }, () => { this.cardOpacity = 1 }) }, 150) }五、周期切换的组件化封装
5.1 通用周期切换组件
@Component export struct PeriodSwitcher { @Prop periods: string[] = [] @State selectedIndex: number = 0 @BuilderParam onChange?: (index: number) => void build() { Row({ space: 0 }) { ForEach(this.periods, (period: string, index: number) => { Text(period) .fontSize(14) .fontColor(this.selectedIndex === index ? '#FFFFFF' : '#666666') .fontWeight(this.selectedIndex === index ? FontWeight.Bold : FontWeight.Normal) .padding({ left: 20, right: 20, top: 8, bottom: 8 }) .backgroundColor(this.selectedIndex === index ? '#F5A623' : Color.Transparent) .borderRadius(16) .onClick(() => { this.selectedIndex = index if (this.onChange) { this.onChange(index) } }) }) } .width('100%') .justifyContent(FlexAlign.Center) } }六、周期切换的耐久性
6.1 测试用例
describe('PeriodSwitcher', () => { it('should default to weekly', () => { expect(selectedPeriod).toBe(0) }) it('should switch to monthly', () => { selectedPeriod = 1 expect(selectedPeriod).toBe(1) }) it('should filter data correctly', () => { selectedPeriod = 0 const weekly = filteredStats selectedPeriod = 1 const monthly = filteredStats expect(monthly.diaryCount).toBeGreaterThanOrEqual(weekly.diaryCount) }) })七、周期切换与数据持久化
7.1 保存当前周期
// 保存用户选择的周期偏好 async savePeriodPreference(): Promise<void> { const pref = await preferences.getPreferences(this.context, 'stats_pref') await pref.put('selectedPeriod', this.selectedPeriod) await pref.flush() } // 加载周期偏好 async loadPeriodPreference(): Promise<void> { const pref = await preferences.getPreferences(this.context, 'stats_pref') this.selectedPeriod = await pref.get('selectedPeriod', 0) }八、不同周期的图表适配
8.1 柱状图数据适配
// 根据周期返回不同的柱状图数据 get chartData(): number[] { switch (this.selectedPeriod) { case 0: return this.getWeeklyChartData() // 7 天 case 1: return this.getMonthlyChartData() // 30 天 case 2: return this.getYearlyChartData() // 12 个月 } } getWeeklyChartData(): number[] { // 返回最近 7 天的日记数量 return [3, 5, 4, 7, 6, 8, 5] } getMonthlyChartData(): number[] { // 返回最近 30 天的日记数量(按周聚合) return [25, 30, 28, 35] } getYearlyChartData(): number[] { // 返回 12 个月的日记数量 return [120, 135, 128, 145, 140, 150, 138, 142, 155, 148, 160, 152] }8.2 图表标签适配
// 根据周期生成 X 轴标签 get xAxisLabels(): string[] { switch (this.selectedPeriod) { case 0: return ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] case 1: return ['第1周', '第2周', '第3周', '第4周'] case 2: return ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'] } }九、周期切换的响应式适配
9.1 不同屏幕适配
| 屏幕宽度 | 按钮内边距 | 字号 | 布局 |
|---|---|---|---|
| < 360vp | 14vp | 12fp | 紧凑 |
| 360-414vp | 20vp | 14fp | 标准 |
| 414-600vp | 24vp | 15fp | 宽松 |
| > 600vp | 28vp | 16fp | 舒适 |
十、最佳实践
7.1 周期切换设计原则
有序列表 — 周期切换设计的 5 个原则:
- 选项清晰:周/月/年三种周期,覆盖常见时间范围
- 选中高亮:使用品牌色突出选中项
- 数据联动:切换周期时,下方所有统计卡片同步更新
- 动画过渡:切换时使用淡入淡出动画,体验流畅
- 组件复用:封装为通用 PeriodSwitcher 组件
7.2 萌宠日记周期切换总结
| 设计要素 | 值 | 说明 |
|---|---|---|
| 周期选项 | 周、月、年 | 3 种 |
| 选中背景 | #F5A623橙色 | 高亮 |
| 未选中背景 | 透明 | 低调 |
| 数据过滤 | 按日期范围 | 周/月/年过滤 |
| 动画 | 淡入淡出 150ms | 平滑过渡 |
| 组件化 | PeriodSwitcher | 可复用 |
总结
本文从萌宠日记的统计周期切换实现出发,深入解析了周期切换的完整方案:
- 胶囊按钮组:选中橙色,未选中透明
- 选中态管理:@State selectedPeriod 控制
- 数据过滤:根据周期切换显示不同数据
- 各周期计算:周/月/年数据聚合
- 统计卡片联动:切换时所有卡片同步更新
- 切换动画:淡入淡出过渡
- 组件化封装:通用 PeriodSwitcher 组件
- 测试用例:验证切换逻辑正确性
- 数据持久化:Preferences 保存周期偏好
- 图表适配:不同周期不同数据粒度和标签
- 无障碍设计:accessibilityText 描述周期状态
- 响应式适配:不同屏幕尺寸动态调整
13.2 周期切换与其他统计功能的配合
周期切换与统计页面的其他功能紧密配合:
| 功能 | 与周期切换的配合 | 说明 |
|---|---|---|
| 日记数量统计 | 显示选定周期内的日记数 | 周/月/年不同粒度 |
| 心情分布图 | 显示选定周期内的心情占比 | 数据随周期变化 |
| 活动统计 | 显示选定周期内的活动次数 | 更精确的趋势分析 |
| 柱状图 | X 轴标签随周期变化 | 天/周/月不同维度 |
13.3 周期切换的使用场景
| 场景 | 推荐周期 | 说明 |
|---|---|---|
| 查看近期趋势 | 周 | 最近 7 天数据 |
| 查看月度总结 | 月 | 当月数据汇总 |
| 查看年度报告 | 年 | 全年数据趋势 |
| 对比不同月份 | 月 | 切换月份查看 |
| 对比不同年份 | 年 | 切换年份查看 |
13.4 常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 切换周期后数据未更新 | 数据过滤逻辑未正确实现 | 检查 filteredStats 计算属性 |
| 按钮样式不切换 | @State 未正确绑定 | 检查 selectedPeriod 状态更新 |
| 柱状图不随周期变化 | 图表数据未绑定到周期 | 使用 chartData 计算属性 |
| 动画卡顿 | 数据量过大 | 优化数据计算逻辑,减少渲染 |
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- Row 组件:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-row
- 颜色使用规范:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/color-usage
- @State 装饰器:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state
- @Component 组件:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-create-custom-components
- 动画开发:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-animation
- 数据管理:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/state-management
- 响应式布局:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/responsive-layout
- 开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
- Preferences 使用:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/data-persistence-by-preferences
- 条件渲染:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-condition-variable-introduction
- 计算属性:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-get-started
- 无障碍开发:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/accessibility-kit
- 数据过滤与排序:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/array-sort
- 应用架构设计:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/stage-model-development-overview
- 图表绘制基础:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-stack
- ForEach 使用:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-foreach
- 应用数据管理:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/app-data-persistence
- 数据可视化:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/data-visualization
- 日期处理:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/date-time
- 统计功能开发:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/statistics
- 用户体验设计:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ux-design
- 交互反馈设计:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/interaction-feedback
- 应用性能优化:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/performance-optimization
- 应用测试指南:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/testing
- 应用安全开发:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/security
- 组件化开发:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/component-based-design
- 状态管理:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state-management
- 布局基础:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkui-layout
