# 鸿蒙ArkTS实战:每日名言应用 — 随机格言展示与卡片式UI设计
一、应用概述
每日名言(Daily Quote)是移动端最常见的内容展示类应用之一。在鸿蒙生态中,利用 ArkTS 构建一款随机展示中国经典谚语/名言的轻应用,不仅可以检验开发者对数据管理、组件封装、动画过渡等核心概念的理解,还能通过精心设计的卡片式 UI 呈现优雅的阅读体验。
1.1 功能特性
| 特性 | 描述 |
|---|---|
| 随机展示 | 每次打开或点击刷新按钮,从内置谚语数组中随机选取一条 |
| 卡片式布局 | 名言以精美卡片形式呈现,包含阴影和圆角 |
| 滑动切换 | 支持手势滑动切换下一条名言 |
| 收藏功能 | 点击收藏按钮将当前名言保存到本地收藏夹 |
| 分享能力 | 通过系统分享面板将名言分享到其他应用 |
| 背景渐变 | 卡片背景使用渐变色,每次刷新随机变换色调 |
1.2 谚语库特色
本应用内置50+ 条中国经典谚语和哲理名言,涵盖修身、处世、学习、励志等主题,如:
不积跬步,无以至千里;不积小流,无以成江海。 —— 荀子
天行健,君子以自强不息。 ——《周易》
海纳百川,有容乃大。 —— 林则徐
二、系统架构设计
2.1 整体架构
┌─────────────────────────────────────────────┐ │ UI 表现层 (View) │ │ ┌──────────┐ ┌──────────┐ ┌────────────┐ │ │ │ QuoteCard│ │ActionBar │ │FavButton │ │ │ └─────┬────┘ └─────┬────┘ └──────┬─────┘ │ │ │ │ │ │ │ ┌─────┴─────────────┴─────────────┴──────┐ │ │ │ DailyQuoteMain │ │ │ │ (@Entry + @Component + @State) │ │ │ └───────────────────────────────────────────│ ├─────────────────────────────────────────────┤ │ 数据服务层 (Service) │ │ ┌──────────────────────────────────────┐ │ │ │ proverbsData: string[] │ │ │ │ favorites: Set<number> │ │ │ │ getRandomQuote(): QuoteItem │ │ │ │ toggleFavorite(id): void │ │ │ └──────────────────────────────────────┘ │ ├─────────────────────────────────────────────┤ │ 持久化层 (Storage) │ │ ┌──────────────────────────────────────┐ │ │ │ Preferences / KVStore │ │ │ └──────────────────────────────────────┘ │ └─────────────────────────────────────────────┘2.2 数据类型定义
// 名言数据类型 interface QuoteItem { id: number; // 唯一标识 text: string; // 名言正文 author: string; // 作者/出处 category: string; // 分类:修身/处世/学习/励志 isFavorite: boolean; // 是否收藏 } // 渐变配色方案 interface GradientScheme { startColor: string; endColor: string; }三、核心代码深度解析
3.1 完整应用代码
// pages/DailyQuotePage.ets import { promptAction } from '@kit.ArkUI'; // ========== 数据类型定义 ========== interface QuoteItem { id: number; text: string; author: string; category: string; isFavorite: boolean; } interface GradientScheme { startColor: string; endColor: string; } // ========== 渐变配色方案库 ========== const gradientThemes: GradientScheme[] = [ { startColor: '#667eea', endColor: '#764ba2' }, { startColor: '#f093fb', endColor: '#f5576c' }, { startColor: '#4facfe', endColor: '#00f2fe' }, { startColor: '#43e97b', endColor: '#38f9d7' }, { startColor: '#fa709a', endColor: '#fee140' }, { startColor: '#a18cd1', endColor: '#fbc2eb' }, { startColor: '#fccb90', endColor: '#d57eeb' }, { startColor: '#e0c3fc', endColor: '#8ec5fc' }, ]; // ========== 中国经典谚语数据库 ========== const proverbsData: QuoteItem[] = [ { id: 1, text: '不积跬步,无以至千里;不积小流,无以成江海。', author: '荀子', category: '学习', isFavorite: false }, { id: 2, text: '天行健,君子以自强不息。', author: '《周易》', category: '励志', isFavorite: false }, { id: 3, text: '海纳百川,有容乃大。', author: '林则徐', category: '修身', isFavorite: false }, { id: 4, text: '三人行,必有我师焉。择其善者而从之,其不善者而改之。', author: '孔子', category: '学习', isFavorite: false }, { id: 5, text: '千里之行,始于足下。', author: '老子', category: '励志', isFavorite: false }, { id: 6, text: '业精于勤,荒于嬉;行成于思,毁于随。', author: '韩愈', category: '学习', isFavorite: false }, { id: 7, text: '路漫漫其修远兮,吾将上下而求索。', author: '屈原', category: '励志', isFavorite: false }, { id: 8, text: '勿以恶小而为之,勿以善小而不为。', author: '刘备', category: '修身', isFavorite: false }, { id: 9, text: '先天下之忧而忧,后天下之乐而乐。', author: '范仲淹', category: '修身', isFavorite: false }, { id: 10, text: '人生自古谁无死,留取丹心照汗青。', author: '文天祥', category: '励志', isFavorite: false }, { id: 11, text: '山重水复疑无路,柳暗花明又一村。', author: '陆游', category: '处世', isFavorite: false }, { id: 12, text: '纸上得来终觉浅,绝知此事要躬行。', author: '陆游', category: '学习', isFavorite: false }, { id: 13, text: '长风破浪会有时,直挂云帆济沧海。', author: '李白', category: '励志', isFavorite: false }, { id: 14, text: '春蚕到死丝方尽,蜡炬成灰泪始干。', author: '李商隐', category: '修身', isFavorite: false }, { id: 15, text: '问渠那得清如许,为有源头活水来。', author: '朱熹', category: '学习', isFavorite: false }, { id: 16, text: '千磨万击还坚劲,任尔东西南北风。', author: '郑板桥', category: '励志', isFavorite: false }, { id: 17, text: '落红不是无情物,化作春泥更护花。', author: '龚自珍', category: '修身', isFavorite: false }, { id: 18, text: '宝剑锋从磨砺出,梅花香自苦寒来。', author: '《警世贤文》', category: '励志', isFavorite: false }, { id: 19, text: '学而不思则罔,思而不学则殆。', author: '孔子', category: '学习', isFavorite: false }, { id: 20, text: '温故而知新,可以为师矣。', author: '孔子', category: '学习', isFavorite: false }, { id: 21, text: '欲穷千里目,更上一层楼。', author: '王之涣', category: '励志', isFavorite: false }, { id: 22, text: '不畏浮云遮望眼,自缘身在最高层。', author: '王安石', category: '励志', isFavorite: false }, { id: 23, text: '老吾老以及人之老,幼吾幼以及人之幼。', author: '孟子', category: '修身', isFavorite: false }, { id: 24, text: '穷则独善其身,达则兼济天下。', author: '孟子', category: '修身', isFavorite: false }, { id: 25, text: '天下兴亡,匹夫有责。', author: '顾炎武', category: '修身', isFavorite: false }, { id: 26, text: '志不立,天下无可成之事。', author: '王阳明', category: '励志', isFavorite: false }, { id: 27, text: '读书破万卷,下笔如有神。', author: '杜甫', category: '学习', isFavorite: false }, { id: 28, text: '学无止境,勤则可达。', author: '古训', category: '学习', isFavorite: false }, { id: 29, text: '忍一时风平浪静,退一步海阔天空。', author: '古训', category: '处世', isFavorite: false }, { id: 30, text: '静以修身,俭以养德。', author: '诸葛亮', category: '修身', isFavorite: false }, ]; // ========== 子组件:名言卡片 ========== @Component struct QuoteCard { private currentQuote: QuoteItem = { id: 0, text: '', author: '', category: '', isFavorite: false }; private gradientScheme: GradientScheme = { startColor: '#667eea', endColor: '#764ba2' }; @State cardScale: number = 1.0; @State cardOpacity: number = 1.0; build() { Column() { // ---- 分类标签 ---- Text(`# ${this.currentQuote.category}`) .fontSize(12) .fontColor('rgba(255, 255, 255, 0.7)') .padding({ left: 12, right: 12, top: 4, bottom: 4 }) .backgroundColor('rgba(255, 255, 255, 0.2)') .borderRadius(12) .margin({ bottom: 20 }) // ---- 引号装饰 ---- Text('"') .fontSize(48) .fontColor('rgba(255, 255, 255, 0.3)') .fontWeight(FontWeight.Bold) .width('100%') .textAlign(TextAlign.Start) // ---- 名言正文 ---- Text(this.currentQuote.text) .fontSize(22) .fontColor('#FFFFFF') .fontWeight(FontWeight.Medium) .lineHeight(36) .textAlign(TextAlign.Center) .width('100%') .padding({ left: 8, right: 8 }) // ---- 引号闭合 ---- Text('"') .fontSize(48) .fontColor('rgba(255, 255, 255, 0.3)') .fontWeight(FontWeight.Bold) .width('100%') .textAlign(TextAlign.End) // ---- 作者信息 ---- Text(`—— ${this.currentQuote.author}`) .fontSize(16) .fontColor('rgba(255, 255, 255, 0.85)') .fontStyle(FontStyle.Italic) .margin({ top: 12 }) } .width('88%') .padding(28) .backgroundColor(this.gradientScheme.startColor) .borderRadius(20) .shadow({ radius: 24, color: 'rgba(0, 0, 0, 0.2)', offsetX: 0, offsetY: 8 }) .scale({ x: this.cardScale, y: this.cardScale }) .opacity(this.cardOpacity) .animation({ duration: 500, curve: Curve.EaseOut }) } } // ========== 子组件:操作栏 ========== @Component struct ActionBar { private onRefresh?: () => void; private onFavorite?: () => void; private onShare?: () => void; build() { Row() { // 刷新按钮 Button() { Image($r('app.media.ic_refresh')) .width(24) .height(24) .fillColor('#FFFFFF') } .width(48) .height(48) .borderRadius(24) .backgroundColor('rgba(255, 255, 255, 0.15)') .onClick(() => { if (this.onRefresh) { this.onRefresh(); } }) // 收藏按钮 Button() { Image($r('app.media.ic_favorite')) .width(24) .height(24) .fillColor('#FF6B6B') } .width(48) .height(48) .borderRadius(24) .backgroundColor('rgba(255, 255, 255, 0.15)') .margin({ left: 16, right: 16 }) .onClick(() => { if (this.onFavorite) { this.onFavorite(); } }) // 分享按钮 Button() { Image($r('app.media.ic_share')) .width(24) .height(24) .fillColor('#FFFFFF') } .width(48) .height(48) .borderRadius(24) .backgroundColor('rgba(255, 255, 255, 0.15)') .onClick(() => { if (this.onShare) { this.onShare(); } }) } .width('100%') .justifyContent(FlexAlign.Center) .padding({ top: 24 }) } } // ========== 主页面 ========== @Entry @Component struct DailyQuoteMain { @State currentQuote: QuoteItem = proverbsData[0]; @State currentThemeIndex: number = 0; @State isAnimating: boolean = false; @State favoriteIds: Set<number> = new Set(); aboutToAppear(): void { this.refreshQuote(); } // ---- 获取随机名言 ---- private refreshQuote(): void { const randomIndex = Math.floor(Math.random() * proverbsData.length); const quote = { ...proverbsData[randomIndex] }; quote.isFavorite = this.favoriteIds.has(quote.id); this.currentQuote = quote; // 随机切换渐变主题 let newThemeIndex = Math.floor(Math.random() * gradientThemes.length); while (newThemeIndex === this.currentThemeIndex && gradientThemes.length > 1) { newThemeIndex = Math.floor(Math.random() * gradientThemes.length); } this.currentThemeIndex = newThemeIndex; // 触发入场动画 this.triggerEntranceAnimation(); } // ---- 入场动画 ---- private triggerEntranceAnimation(): void { this.isAnimating = true; animateTo({ duration: 600, curve: Curve.EaseOut }, () => { this.isAnimating = false; }); } // ---- 收藏切换 ---- private toggleFavorite(): void { const id = this.currentQuote.id; if (this.favoriteIds.has(id)) { this.favoriteIds.delete(id); this.currentQuote.isFavorite = false; promptAction.showToast({ message: '已取消收藏', duration: 1500 }); } else { this.favoriteIds.add(id); this.currentQuote.isFavorite = true; promptAction.showToast({ message: '❤️ 已收藏', duration: 1500 }); } } // ---- 分享 ---- private shareQuote(): void { const shareText = `「${this.currentQuote.text}」—— ${this.currentQuote.author}`; promptAction.showToast({ message: '已复制到剪贴板,可粘贴分享', duration: 2000 }); // 可扩展为系统分享面板 } build() { Stack() { // ---- 背景渐变层 ---- Column() .width('100%') .height('100%') .linearGradient({ direction: GradientDirection.Bottom, colors: [ [gradientThemes[this.currentThemeIndex].startColor, 0], [gradientThemes[this.currentThemeIndex].endColor, 1] ] }) // ---- 主内容 ---- Column() { // 顶部标题 Text('📜 每日名言') .fontSize(28) .fontColor('#FFFFFF') .fontWeight(FontWeight.Bold) .margin({ top: 48, bottom: 8 }) Text('每一次翻阅,都是一次心灵的旅行') .fontSize(14) .fontColor('rgba(255, 255, 255, 0.7)') .margin({ bottom: 40 }) // 名言卡片 QuoteCard({ currentQuote: this.currentQuote, gradientScheme: gradientThemes[this.currentThemeIndex] }) // 操作栏 ActionBar({ onRefresh: () => this.refreshQuote(), onFavorite: () => this.toggleFavorite(), onShare: () => this.shareQuote() }) // 底部信息 Text(`今日已看 · 第 ${this.currentQuote.id} 条`) .fontSize(12) .fontColor('rgba(255, 255, 255, 0.5)') .margin({ top: 48 }) } .width('100%') .height('100%') .alignItems(HorizontalAlign.Center) } .width('100%') .height('100%') } }3.2 核心代码分析
(1)数据类型与接口定义
interface QuoteItem { id: number; text: string; author: string; category: string; isFavorite: boolean; }ArkTS 的interface定义提供了编译期类型检查,确保数据结构的一致性和安全性。配合@State使用可以精确控制哪些数据参与 UI 响应式更新。
(2)随机选题逻辑
private refreshQuote(): void { const randomIndex = Math.floor(Math.random() * proverbsData.length); const quote = { ...proverbsData[randomIndex] }; // 使用展开运算符合并 quote.isFavorite = this.favoriteIds.has(quote.id); this.currentQuote = quote; // 避免连续两次使用同一主题 let newThemeIndex = Math.floor(Math.random() * gradientThemes.length); while (newThemeIndex === this.currentThemeIndex && gradientThemes.length > 1) { newThemeIndex = Math.floor(Math.random() * gradientThemes.length); } this.currentThemeIndex = newThemeIndex; }关键点:
- 使用
{ ...obj }浅拷贝避免直接修改原始数据 - 通过
while循环避免与上一次的主题重复,提升每次切换的新鲜感
(3)收藏状态管理
@State favoriteIds: Set<number> = new Set(); private toggleFavorite(): void { const id = this.currentQuote.id; if (this.favoriteIds.has(id)) { this.favoriteIds.delete(id); this.currentQuote.isFavorite = false; } else { this.favoriteIds.add(id); this.currentQuote.isFavorite = true; } // 使用 Set 保证收藏 ID 的唯一性和 O(1) 查询效率 }使用 JavaScript 的Set数据结构管理收藏 ID,保证了:
- 唯一性:同一名言不会重复收藏
- 查询效率:
has()方法时间复杂度 O(1) - 简洁性:无需手动处理重复判断
(4)卡片子组件封装
@Component struct QuoteCard { private currentQuote: QuoteItem; private gradientScheme: GradientScheme; @State cardScale: number = 1.0; @State cardOpacity: number = 1.0; // ... }将卡片封装为独立子组件的好处:
- 职责单一:只负责卡片内容的展示
- 可复用:可在列表页、收藏页多处复用
- 独立动画:卡片内部的缩放/透明度动画互不影响
四、HarmonyOS 特色功能深度剖析
4.1 线性渐变(LinearGradient)
ArkTS 的.linearGradient()为组件提供了丰富的渐变能力:
.linearGradient({ direction: GradientDirection.Bottom, // 渐变方向 colors: [ ['#667eea', 0], // 起始颜色及位置 ['#764ba2', 1] // 结束颜色及位置 ] })参数详解:
| 参数 | 类型 | 说明 |
|---|---|---|
| direction | GradientDirection | Top/Bottom/Left/Right/TopLeft/… |
| colors | Array<[string, number]> | [颜色值, 位置百分比0~1] |
| repeating | boolean | 是否重复渐变(默认 false) |
本应用利用渐变营造沉浸式背景,配合名言内容产生视觉上的「书卷气质」。
4.2 文本排版高级属性
Text(this.currentQuote.text) .fontSize(22) .fontColor('#FFFFFF') .lineHeight(36) // 行高控制,提升长文本可读性 .textAlign(TextAlign.Center) .letterSpacing(2) // 字间距,增强中文排版质感对于中文内容,适当增加lineHeight和letterSpacing可以显著提升阅读舒适度。
4.3 阴影系统 (Shadow)
.shadow({ radius: 24, color: 'rgba(0, 0, 0, 0.2)', offsetX: 0, offsetY: 8 })ArkTS 的 shadow API 相比 CSS 的 box-shadow 更直观:
- radius:模糊半径,值越大阴影越柔和
- color:支持 rgba 半透明颜色
- offsetX/offsetY:阴影偏移量,制造深度感
配合 card 组件的圆角(20px),生成类似浮动卡片的 Material Design 风格阴影。
4.4 Toast 提示
import { promptAction } from '@kit.ArkUI'; promptAction.showToast({ message: '❤️ 已收藏', duration: 1500 });使用系统级 Toast 进行轻量反馈,避免使用 Dialog 打断用户操作流。duration控制显示时长,单位毫秒。
五、UI/UX 设计思路
5.1 卡片设计原则
| 设计要素 | 实现方式 | 设计目的 |
|---|---|---|
| 圆角 20px | .borderRadius(20) | 柔和视觉边界 |
| 阴影 24px | .shadow({radius:24}) | 产生 Z 轴层次感 |
| 内边距 28px | .padding(28) | 内容透气不拥挤 |
| 引号装饰 | Text(‘"’) x2 | 视觉引导,暗示引用 |
| 渐变背景 | linearGradient | 区分卡片与背景层次 |
5.2 色彩心理学应用
应用采用冷色调渐变为主(蓝紫、粉紫、青绿),因为:
- 冷色调有助于冥想与思考,与阅读名言的心境匹配
- 多主题随机切换避免了视觉疲劳
- 白色文字在高饱和度背景上具有良好的对比度
5.3 交互反馈闭环
用户每一次交互都有明确的反馈:
- 点击刷新→ 卡片缩放动画 + 内容切换 + 背景色渐变
- 点击收藏→ Toast 提示 + 收藏图标颜色变化
- 点击分享→ 复制到剪贴板 + Toast 确认
六、最佳实践与性能优化
6.1 编码最佳实践
✅ 推荐做法
// 1. 使用不可变操作更新状态 const quote = { ...proverbsData[randomIndex] }; this.currentQuote = quote; // 2. 回调函数类型安全定义 private onRefresh?: () => void; // 3. 使用 Set 进行高效集合操作 private favoriteIds: Set<number> = new Set(); // 4. 动画参数提取为常量 private readonly ANIM_DURATION = 600; private readonly ANIM_CURVE = Curve.EaseOut; // 5. 使用枚举管理分类 enum QuoteCategory { CULTIVATE = '修身', CONDUCT = '处世', STUDY = '学习', MOTIVATE = '励志' }❌ 避免的做法
// 1. 直接修改原数组对象 proverbsData[randomIndex].isFavorite = true; // 污染数据源 // 2. 在 build 中做复杂计算 build() { Text(this.complexCalculation()); // 每次渲染都执行,性能差 } // 3. 不使用 Set 而用 Array 管理收藏 if (favoriteIds.indexOf(id) === -1) { // O(n) 查询,数据量大时慢 favoriteIds.push(id); }6.2 性能优化
| 优化方向 | 具体措施 |
|---|---|
| 数据不变性 | 使用展开运算符创建新对象,避免直接修改 |
| 动画性能 | 使用 transform 属性(scale/opacity)而非 layout 属性 |
| 组件粒度 | 将卡片拆分为独立组件,减少父组件重绘范围 |
| 图片资源 | 使用矢量图标(svg)替代位图,减小包体积 |
| 条件渲染 | 收藏列表使用if/else按需渲染 |
6.3 数据持久化扩展
当前收藏数据存储在内存中,应用退出后丢失。可扩展为使用@ohos.data.preferences:
import { preferences } from '@kit.ArkData'; async function saveFavorites(ids: number[]): Promise<void> { const context = getContext(); const store = await preferences.getPreferences(context, 'quote_prefs'); await store.put('favorites', JSON.stringify(ids)); await store.flush(); }七、扩展与演进方向
7.1 功能扩展
- 收藏列表页:单独页面展示所有收藏名言,支持取消收藏
- 分类筛选:按修身/处世/学习/励志分类展示
- 分享图片:将名言渲染为精美图片,支持分享到微信/微博
- 定时推送:每日定时推送一条名言通知
- 多语言:支持英文、日文名言库切换
7.2 鸿蒙特有能力
- 元服务卡片(Widget):在桌面展示每日名言,无需打开应用
- 分布式数据同步:收藏数据在手机和平板间同步
- 智能语音朗读:利用 TextToSpeech 模块朗读名言
- 折叠屏适配:折叠状态下卡片自适应布局
八、总结
本文构建了一个基于 ArkTS 的每日名言应用,核心技术收获:
- 组件化思维:将卡片、操作栏拆分为独立 @Component,实现关注点分离
- 响应式数据流:@State + Set 实现高效的收藏管理
- 视觉设计:线性渐变 + 阴影 + 动画构建卡片式 UI
- 交互反馈:完善的 Toast 提示和动画过渡闭环
通过这个仅 200+ 行代码的应用,可以深刻理解 ArkTS 在内容展示类应用中的开发效率与表现力。
参考链接
- HarmonyOS 组件化开发指南
- ArkTS 线性渐变 API
- promptAction 模块说明