# 鸿蒙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 在内容展示类应用中的开发效率与表现力。
参考链接
更多推荐



所有评论(0)