一、应用概述

每日名言(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)        // 字间距,增强中文排版质感

对于中文内容,适当增加 lineHeightletterSpacing 可以显著提升阅读舒适度。

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 交互反馈闭环

用户每一次交互都有明确的反馈:

  1. 点击刷新 → 卡片缩放动画 + 内容切换 + 背景色渐变
  2. 点击收藏 → Toast 提示 + 收藏图标颜色变化
  3. 点击分享 → 复制到剪贴板 + 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 功能扩展

  1. 收藏列表页:单独页面展示所有收藏名言,支持取消收藏
  2. 分类筛选:按修身/处世/学习/励志分类展示
  3. 分享图片:将名言渲染为精美图片,支持分享到微信/微博
  4. 定时推送:每日定时推送一条名言通知
  5. 多语言:支持英文、日文名言库切换

7.2 鸿蒙特有能力

  • 元服务卡片(Widget):在桌面展示每日名言,无需打开应用
  • 分布式数据同步:收藏数据在手机和平板间同步
  • 智能语音朗读:利用 TextToSpeech 模块朗读名言
  • 折叠屏适配:折叠状态下卡片自适应布局

八、总结

本文构建了一个基于 ArkTS 的每日名言应用,核心技术收获:

  1. 组件化思维:将卡片、操作栏拆分为独立 @Component,实现关注点分离
  2. 响应式数据流:@State + Set 实现高效的收藏管理
  3. 视觉设计:线性渐变 + 阴影 + 动画构建卡片式 UI
  4. 交互反馈:完善的 Toast 提示和动画过渡闭环

通过这个仅 200+ 行代码的应用,可以深刻理解 ArkTS 在内容展示类应用中的开发效率与表现力。


参考链接

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐