一、前言

1.1 为什么写一个名言生成器

在快节奏的移动互联网时代,人们打开手机往往是为了寻求片刻的安宁。一句恰到好处的话,可能就能点亮一个人的一整天。这就是名言生成器这类 App 存在的意义——它不解决具体问题,但它提供情绪价值。

从技术角度看,名言生成器是一个"小而美"的经典练手项目:

  • 数据层 —— 一个字符串数组,存储所有名言
  • 逻辑层 —— 一个随机选取算法,保证不重复
  • 表现层 —— 优雅的排版和动画,让文字更有力量

麻雀虽小,五脏俱全。它涵盖了 ArkTS 开发中最重要的基础能力:状态管理、数组操作、事件响应、UI 样式。更重要的是,它让我们有机会深入探讨用户体验设计——这不是一个功能驱动型 App,而是一个情绪驱动型 App,设计上需要考虑的远不止"能不能用"。

1.2 最终效果预览

先看最终要实现的页面:

+----------------------------------+
|                                  |
|                                  |
|    "心有山海,静而不争。"         |
|                                  |
|                                  |
|    [      换一句       ]          |
|                                  |
|      粉紫渐变背景                 |
|      圆角按钮                     |
|      温柔的整体风格               |
+----------------------------------+

全屏居中,一句名言 + 一个按钮。点击按钮随机切换下一句,且不会和上一句重复。背景是粉紫到浅蓝的渐变,按钮是柔和的粉色圆角设计。

1.3 本文结构

本文将按照以下顺序展开:

  1. 完整代码展示 —— 先看全貌,建立整体认知
  2. 数据设计 —— 名言列表的结构与扩展
  3. 随机算法 —— 从朴素实现到防重复的演进
  4. UI 设计 —— 从布局到配色,打造治愈感
  5. 状态管理 —— @State 在数组场景中的使用
  6. 扩展与进阶 —— 动画、分类、网络加载、本地存储
  7. 设计理念 —— 为什么"温柔"是一种值得追求的设计风格
  8. 性能与最佳实践 —— 数组操作、渲染优化、代码组织

二、完整代码

@Entry
@Component
struct QuoteGenerator {
  // 名言列表(可自行扩展)
  @State quoteList: string[] = [
    "生活最好的状态,是冷冷清清的风风火火。",
    "心有山海,静而不争。",
    "慢慢来,好戏都在烟火里。",
    "你若盛开,清风自来。",
    "保持热爱,奔赴山海。",
    "往事不回头,未来不将就。",
    "心向阳光,何惧风霜。",
    "平凡日子,也藏着温柔的光。",
    "愿你历尽千帆,归来仍是少年。",
    "人生没有白走的路,每一步都算数。"
  ];
  // 当前显示的名言索引
  @State currentIndex: number = 0;

  // 随机切换名言
  changeQuote() {
    let newIndex: number;
    // 避免和上一句重复
    do {
      newIndex = Math.floor(Math.random() * this.quoteList.length);
    } while (newIndex === this.currentIndex);
    this.currentIndex = newIndex;
  }

  build() {
    Column({ space: 40 }) {
      // 名言文本
      Text(this.quoteList[this.currentIndex])
        .fontSize(22)
        .fontWeight(FontWeight.Medium)
        .lineHeight(35)
        .letterSpacing(1)
        .textAlign(TextAlign.Center)
        .padding(20)

      // 刷新按钮
      Button("换一句")
        .fontSize(18)
        .width(160)
        .height(50)
        .borderRadius(25)
        .backgroundColor("#FF7A85")
        .fontColor(Color.White)
        .onClick(() => {
          this.changeQuote();
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    // 渐变背景(粉紫到浅蓝)
    .linearGradient({
      direction: GradientDirection.Bottom,
      colors: [[0xFFF5F7FA, 0.0], [0xFFE4EAF5, 1.0]]
    })
    .padding(30)
  }
}

一共 57 行代码,完成了从数据定义、算法逻辑到 UI 展现的全链路。

下面逐层拆解。


三、数据设计:名言列表的结构哲学

3.1 @State 修饰数组

@State quoteList: string[] = [
  "生活最好的状态,是冷冷清清的风风火火。",
  "心有山海,静而不争。",
  // ... 共 10 句
];

@State 修饰一个 string[] 数组。在 ArkTS 中,数组作为引用类型,其变化追踪有一些需要特别注意的地方。

直接修改数组元素会触发更新吗?

// ❌ 这样可能不会触发 UI 更新
this.quoteList[3] = "新的名言";

// ✅ 这样一定会触发
this.quoteList = [...this.quoteList];
this.quoteList[3] = "新的名言";
this.quoteList = this.quoteList.slice();

// ✅ 使用数组方法(push、splice 等通常可以触发)
this.quoteList.push("新的名言");

为什么直接下标赋值可能不触发?因为 ArkTS 的 @State 追踪的是变量的引用变化,而不是对象内部属性的变化。数组方法如 pushsplicepop 等之所以能触发,是因为框架对常见数组方法做了特殊处理——它们会修改数组的 length 和内部元素,框架检测到这些变化后触发更新。

但最稳妥的方式始终是:创建新数组赋值给 @State 变量。这保证了行为的一致性,避免踩坑。

3.2 名言的选择标准

我选的这 10 句名言有一个共同点:治愈感

这不是偶然的。名言生成器的用户打开它,不是为了获取知识,而是为了获得情感共鸣。因此选句的标准应该是:

维度 高优先级 低优先级
情感价值 温暖的、治愈的、励志的 冷冰冰的道理
语言美感 有韵律、有意象、读起来舒服 直白说教
通用性 适合大多数人的心境 过于个人化
时长 一句话,10-20 字 长篇大论

比如"生活最好的状态,是冷冷清清的风风火火"——这句话有对比(冷冷清清 vs 风风火火),有意象(烟火人间),有态度(平淡中不失热烈),读一遍就能记住。这就是好的名言。

"人生没有白走的路,每一步都算数"——这句话有理有据,有画面感(走路),有哲理感,是典型的"金句"结构。

3.3 数据扩展策略

10 句显然不够。实际发布的名言生成器,至少需要 100 句以上才能让用户有"每次打开都不一样"的体验。

数据扩展有几个方向:

方向一:手动精选

从书籍、电影、名人演讲中摘录。优点是质量可控,缺点是工作量随数量线性增长。

方向二:爬虫采集

从名言网站抓取。但需要注意版权问题,且需要过滤质量参差不齐的内容。

方向三:AIGC 生成

用大模型批量生成金句。优点是效率极高,几分钟就能生成几百条。缺点是需要人工筛选——AI 生成的句子有时"看起来很对,读起来乏味"。

方向四:用户 UGC

让用户自行添加和分享名言。这是最能产生粘性的方式,但也需要审核机制。

在工程实现上,随着数据量增加,数据存储方式也需要升级:

10 条     → 硬编码在代码中
50 条     → 单独的 JSON 配置文件
200 条    → 应用内 SQLite 数据库 / 本地文件
1000+ 条  → 云端 API + 本地缓存

四、随机算法:从朴素到优雅

4.1 核心逻辑

changeQuote() {
  let newIndex: number;
  do {
    newIndex = Math.floor(Math.random() * this.quoteList.length);
  } while (newIndex === this.currentIndex);
  this.currentIndex = newIndex;
}

这段代码虽然短,但包含了一个完整的防重复随机算法

4.1.1 Math.random() 的工作原理

Math.random() 返回一个 [0, 1) 之间的浮点数,服从均匀分布。这意味着理论上每个值出现的概率相等。

// 将 [0, 1) 映射到 [0, length)
Math.random() * this.quoteList.length
// 例如 length=10,生成 [0, 10) 的浮点数

// 取整得到整数索引
Math.floor(Math.random() * this.quoteList.length)
// 等概率生成 0, 1, 2, ..., 9

Math.floorMath.ceil 的区别:

Math.random() * 10
生成的浮点数:0.0 ~ 9.999...

Math.floor(...) → 0, 1, 2, ..., 9     (均匀 10 个值)
Math.ceil(...)  → 0, 1, 2, ..., 10    (不均匀,0 的概率极低)

所以这里用 Math.floor 是正确的。

4.1.2 do...while 防重复
do {
  newIndex = Math.floor(Math.random() * this.quoteList.length);
} while (newIndex === this.currentIndex);

这个循环保证:新生成的索引不等于当前索引

这是一个 rejection sampling(拒绝采样) 的典型例子——反复采样直到满足条件。

当列表数量 n=10 时:

  • 每次采样不重复的概率 = 9/10 = 90%
  • 期望循环次数 = 1/(9/10) ≈ 1.11 次
  • 最坏情况(理论上一直重复)概率趋近于 0

当列表数量 n=2 时:

  • 每次采样不重复的概率 = 1/2 = 50%
  • 期望循环次数 = 2 次
  • 还行,性能开销可以忽略

所以这个算法在列表数量 >= 3 时非常高效。

4.1.3 更严格的不重复算法

如果我们想要更严格的随机性——比如让用户看完所有名言之前不重复任何一句——那就需要 Fisher-Yates 洗牌算法了:

@State shuffledList: string[] = []
@State shuffleIndex: number = 0

aboutToAppear() {
  this.resetShuffle()
}

resetShuffle() {
  this.shuffledList = [...this.quoteList]
  // Fisher-Yates 洗牌
  for (let i = this.shuffledList.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [this.shuffledList[i], this.shuffledList[j]] = [this.shuffledList[j], this.shuffledList[i]]
  }
  this.shuffleIndex = 0
}

changeQuote() {
  this.shuffleIndex++
  if (this.shuffleIndex >= this.shuffledList.length) {
    this.resetShuffle()
  }
  // 注意:这里 currentIndex 不再是指向 quoteList 的索引
  // 而是直接获取 shuffledList 中的名言
}

build() {
  Text(this.shuffledList[this.shuffleIndex])
    // ...
}

这个方案的优点:

  1. 用户看到所有名言之前,绝对不会重复
  2. 每一轮的顺序都是随机的
  3. 一轮结束后自动洗牌重新开始

缺点:

  1. 需要额外维护一个打乱后的数组
  2. 需要管理洗牌周期

不过对于名言生成器这种使用场景,简单的 do...while 防重复已经足够。用户不会连续点 100 次按钮——他们通常点一两次,找到触动自己的那句话就够了。


五、UI 设计:为什么粉紫渐变配粉色按钮是"治愈感"的组合

这是整篇文章最具主观性但也最重要的一节。

.linearGradient({
  direction: GradientDirection.Bottom,
  colors: [[0xFFF5F7FA, 0.0], [0xFFE4EAF5, 1.0]]
})

Button("换一句")
  .borderRadius(25)
  .backgroundColor("#FF7A85")

我们逐一分析每个设计决策。

5.1 渐变色分析

背景使用了从上到下的渐变,从 #F5F7FA(近乎白色的浅灰蓝)到 #E4EAF5(淡淡的薰衣草紫/蓝)。

这两个颜色在色彩学上属于低饱和度、高明度的色系。它们的特点是:

  • 不刺眼 —— 高亮度但低饱和度的颜色不会刺激视觉神经,适合长时间注视
  • 有质感 —— 渐变让平面有了"深度",不再单调
  • 情绪暗示 —— 蓝色系给人冷静、安定的感觉;紫色系带有一点点浪漫和温柔

从心理学角度看,冷暖色调融合(蓝色偏冷,薰衣草偏暖)创造了一种平衡感——既有蓝天的开阔,又有花香的温暖。

gradient 的方向是 GradientDirection.Bottom,即从上到下。这意味着在手机竖屏时,浅色在上方,较深(其实也还是很浅)的紫色在下方。这模拟了自然界中天空上亮下暗的规律,符合人的视觉直觉。

5.2 按钮色彩分析

按钮颜色 #FF7A85 是一种温柔的珊瑚粉/玫瑰粉

为什么选择粉色系?

  1. 情感关联 —— 粉色在色彩心理学中与"温柔、关爱、温暖"相关联
  2. 互补色 —— 粉色(偏红)与背景的蓝紫色在色环上邻近,属于类似色搭配,和谐不冲突
  3. 品牌感 —— 这种粉色不是荧光粉(太跳),也不是暗粉(太沉闷),而是恰到好处的"让人想点击"的粉

5.3 圆角设计

.borderRadius(25)

按钮的圆角半径为 25,而按钮高度是 50。25 恰好是高度的一半——这意味着按钮两端是完整的半圆,也就是所谓的"胶囊按钮"(Capsule Button)。

圆角在 UI 设计中的意义:

圆角大小 传达的感觉 典型场景
0(直角) 严谨、硬核 工具型 App、数据面板
4-8 标准、中性 表单、卡片
12-16 柔和、友好 社交、内容型 App
25+(胶囊) 温暖、亲和 情感化、女性向、治愈系

胶囊按钮在视觉上消除了"尖锐感",让人更容易产生点击的欲望。这就是为什么很多情感类 App 都用圆形或胶囊按钮——它们看起来不那么像机器,更像一个邀请

5.4 文字排版

文字排版的每个属性都有其用意:

  • fontSize(22) —— 正文阅读的舒适字号,不大不小,不卑不亢
  • fontWeight(FontWeight.Medium) —— 中粗字重,比常规粗一点,比粗体细一点,有分量感但不压迫
  • lineHeight(35) —— 行高约 1.6 倍字号,留白充裕,读起来不拥挤
  • letterSpacing(1) —— 字间距加 1,让文字更"透气",有时间慢慢品味
  • textAlign(TextAlign.Center) —— 居中排版,对称、庄重、聚焦
  • padding(20) —— 左右留白,防止文字贴边

如果你在手机上打开一个名言生成器,看到这样排版的文字,你的第一感觉应该是"安静"——这就是排版要达到的效果:让文字本身成为视觉主体,而不是排版技巧

5.5 间距与整体平衡

Column({ space: 40 })

Copy

文字和按钮之间的间距是 40。这个间距足够大,让两者各自独立成"块";又不会太大,导致页面显得松散。

为什么是 40 而不是 20 或 60?在 UI 设计中,间距的选择通常遵循 8 点网格体系

间距 = 8 × n
常见值:8, 16, 24, 32, 40, 48, 56, 64

40 = 8 × 5,在这个体系中属于"中等偏大"的间距,适合分隔不同功能区域(文字区 vs 操作区)。


六、状态管理:@State 在 QuoteGenerator 中的应用

6.1 两个状态的角色

@State quoteList: string[] = [...]    // 数据源
@State currentIndex: number = 0       // 当前索引

这两个状态的职责非常清晰:

  • quoteList —— 只读数据,通常不修改(除非用户增删名言)
  • currentIndex —— 唯一的可变状态,驱动 UI 变化

这种一个可变状态驱动 UI的模式,是 ArkTS 中最简单的状态管理模型。数据流是单向的:

用户点击 → changeQuote() → this.currentIndex 变化 → UI 重新渲染

整个数据流清晰可追溯,没有复杂的多状态联动,没有跨组件数据同步。这是最简单的状态管理模式,也是最适合这个场景的模式。

6.2 @State 变化触发的流程

当用户点击按钮时:

  1. onClick 回调执行 this.changeQuote()
  2. changeQuote() 内部修改 this.currentIndex
  3. ArkTS 运行时检测到 @State currentIndex 变化
  4. 框架查找所有读取了 currentIndex 的 UI 节点
  5. 只重新渲染这些节点(在这里就是 Text(this.quoteList[this.currentIndex])
  6. 文字更新,按钮不变,背景不变

这个流程的核心优势是精准——每次点击只更新文字本身,不会触发按钮或背景的重新渲染。在 ArkTS 中,这种依赖追踪在编译期就完成了,所以运行时代价极低。

6.3 状态不可变性原则

虽然 ArkTS 的 @State 可以追踪数组内部变化,但良好的实践是始终将状态视为不可变的

// 推荐的做法:创建新数组
this.quoteList = [...this.quoteList, "新的名言"]

// 不推荐的做法:直接修改原数组再赋值
this.quoteList.push("新的名言")
this.quoteList = this.quoteList  // 可能不会触发更新

不可变性原则带来的好处:

  1. 可预测性 —— 状态变化可追踪,知道什么时候变了、变成了什么
  2. 调试友好 —— 可以轻易地比较前后状态
  3. 性能清晰 —— 新引用一定触发更新,不会出现"改了但没刷新"的问题

七、扩展与进阶

基础功能完成后,我们可以从各个方向扩展这个应用。

7.1 添加切换动画

目前的切换是瞬变的,加上动画会大幅提升体验:

@State quoteOpacity: number = 1

changeQuote() {
  // 先淡出
  this.quoteOpacity = 0

  // 200ms 后换内容再淡入
  setTimeout(() => {
    let newIndex: number
    do {
      newIndex = Math.floor(Math.random() * this.quoteList.length)
    } while (newIndex === this.currentIndex)
    this.currentIndex = newIndex

    // 触发淡入
    this.quoteOpacity = 1
  }, 200)
}

build() {
  Text(this.quoteList[this.currentIndex])
    // ...原有样式不变...
    .opacity(this.quoteOpacity)
    .animation({
      duration: 300,
      curve: Curve.EaseInOut
    })
}

这样切换到下一句时,旧文字淡出,新文字淡入,过渡自然。300ms 的动画时长是人眼感觉"流畅但不拖沓"的最佳区间。

还可以用转场动画实现更丰富的效果:

// 从下方滑入
Text(this.quoteList[this.currentIndex])
  .transition(TransitionEffect.translate({ y: 20 }).combine(TransitionEffect.opacity(0)))

或者模仿翻页效果:

// 3D 旋转翻页
Text(this.quoteList[this.currentIndex])
  .rotate({ x: 0, y: 1, z: 0, angle: this.rotateAngle })

动画是名言生成器的灵魂——物理切换瞬间完成,但情感需要时间过渡。

7.2 分类功能

名言可以按分类展示,让用户选择当前需要的"情绪":

interface QuoteCategory {
  name: string
  icon: string
  quotes: string[]
}

@State categories: QuoteCategory[] = [
  {
    name: '治愈',
    icon: '☀️',
    quotes: ['心有山海,静而不争。', '慢慢来,好戏都在烟火里。', ...]
  },
  {
    name: '励志',
    icon: '🚀',
    quotes: ['保持热爱,奔赴山海。', '人生没有白走的路。', ...]
  },
  {
    name: '爱情',
    icon: '💕',
    quotes: ['你若盛开,清风自来。', ...]
  },
  {
    name: '生活',
    icon: '🍵',
    quotes: ['平凡日子,也藏着温柔的光。', ...]
  }
]

@State currentCategoryIndex: number = 0
@State currentQuoteIndex: number = 0

get currentCategory(): QuoteCategory {
  return this.categories[this.currentCategoryIndex]
}

changeQuote() {
  const quotes = this.currentCategory.quotes
  let newIndex: number
  do {
    newIndex = Math.floor(Math.random() * quotes.length)
  } while (newIndex === this.currentQuoteIndex)
  this.currentQuoteIndex = newIndex
}

UI 上增加分类选择器:

Row({ space: 12 }) {
  ForEach(this.categories, (cat: QuoteCategory, index: number) => {
    Text(cat.icon)
      .fontSize(28)
      .opacity(index === this.currentCategoryIndex ? 1.0 : 0.4)
      .onClick(() => {
        this.currentCategoryIndex = index
        this.currentQuoteIndex = 0
      })
  })
}

这样用户可以在不同情绪主题间切换,应用的可玩性和实用性都大幅提升。

7.3 网络加载更多名言

当本地名言不够用时,可以接入远程 API:

@State isLoading: boolean = false

async fetchMoreQuotes() {
  this.isLoading = true
  try {
    const response = await fetch('https://api.quote.example.com/quotes?count=20')
    const data: string[] = await response.json()
    // 合并到本地列表
    const existing = new Set(this.quoteList)
    const newQuotes = data.filter(q => !existing.has(q))
    this.quoteList = [...this.quoteList, ...newQuotes]
  } catch (e) {
    console.error('获取名言失败', e)
    // 降级:使用本地缓存
  } finally {
    this.isLoading = false
  }
}

这里有一个重要的设计细节:去重。网络获取的名言可能与本地已有内容重复,用 Set 做一次过滤,确保列表中没有重复项。

7.4 本地缓存与收藏

用户可能想把某句名言保存下来:

@State favorites: string[] = []

// 收藏当前名言
toggleFavorite() {
  const current = this.quoteList[this.currentIndex]
  const idx = this.favorites.indexOf(current)
  if (idx >= 0) {
    this.favorites.splice(idx, 1)  // 取消收藏
  } else {
    this.favorites.push(current)    // 收藏
  }
  // 持久化到本地存储
  AppStorage.setOrCreate('favorites', JSON.stringify(this.favorites))
}

// 应用启动时加载收藏
aboutToAppear() {
  const saved = AppStorage.get<string>('favorites')
  if (saved) {
    this.favorites = JSON.parse(saved)
  }
}

ArkTS 提供了 AppStorage 作为全局持久化存储,适合保存用户设置、收藏等轻量级数据。对于更大的数据集(如用户自己导入的数百条名言),推荐使用 Preferences 或关系型数据库。

7.5 分享功能

名言生成器天然适合社交分享:

// 生成分享图片
@State shareImage: PixelMap | null = null

async generateShareImage() {
  const context = getContext(this)
  const imageSource = await context.createImage(this.quoteList[this.currentIndex], {
    width: 1080,
    height: 1920,
    background: '#F5F7FA',
    fontColor: '#333333',
    fontSize: 48,
    lineHeight: 72,
    padding: 80
  })
  this.shareImage = imageSource
  // 调起分享面板
  context.share(this.shareImage)
}

在鸿蒙系统中,分享通过 context.share() 方法调起系统分享面板,用户可以选择保存到相册、发送到微信/微博等。

7.6 自动轮播模式

用户可能希望名言自动切换,就像桌面小组件一样:

@State autoPlay: boolean = false
@State timerId: number = 0
@State interval: number = 5000  // 5秒

toggleAutoPlay() {
  this.autoPlay = !this.autoPlay
  if (this.autoPlay) {
    this.timerId = setInterval(() => {
      this.changeQuote()
    }, this.interval)
  } else {
    clearInterval(this.timerId)
  }
}

// 组件销毁时清理定时器
aboutToDisappear() {
  if (this.timerId) {
    clearInterval(this.timerId)
  }
}

自动轮播模式让名言生成器变成了一个"桌面冥想伴侣"——放在桌上,每隔几秒自动切换一句,营造沉浸的氛围。

需要注意的是:ArkTS 的 setInterval 在不作为前台应用运行时可能会被系统限制频率或暂停。如果要实现后台持久轮播,需要使用 BackgroundTaskManager API 申请后台任务权限。


八、深度思考:设计理念与情绪价值

8.1 为什么"温柔"是一种设计风格

名言生成器这类 App 本质上卖的不是功能,是情绪。用户安装它,是因为在某个时刻感到迷茫、焦虑、或者孤独,需要一句话来给自己一点力量。

因此,整个应用的 UI 设计都必须服务于一个核心目标:让用户感到被理解、被安慰

这就是为什么:

  • 背景用粉紫渐变,而不是黑底金字的"硬核"风格
  • 按钮用珊瑚粉胶囊,而不是棱角分明的矩形按钮
  • 文字字号 22、字间距 1、行高 35,一切都是为了"读起来舒服"
  • 选的名言都是温暖的、鼓励的,没有说教的、批判的

这些看似随意的设计决策,背后都有一个共同的出发点:共情

8.2 功能减法

在开发过程中,一个常见的错误是不断加功能:

  • "加个倒计时吧"
  • "加个每日签到吧"
  • "加个社交广场吧"
  • "加个会员付费吧"

每个功能单独看都有道理,但加在一起就变成了一个"四不像"——用户打开它,看到的是满屏的功能入口,而不是那句他们真正想要的话。

名言生成器的核心竞争力是简单。打开即读,读完即走。任何一个多余的功能都在削弱这种纯粹感。

这就是功能减法的理念:

问:用户打开 App 想做什么?
答:看到一句有共鸣的话。

问:什么会阻碍这个目标?
答:广告、弹窗、注册、引导、复杂交互。

问:我们能做什么来强化这个目标?
答:让文字更美、切换更流畅、内容更丰富(但交互不复杂)。

8.3 什么是"好的"名言列表

最后,关于名言本身。一份好的名言列表,应该具备以下特征:

  1. 多样性 —— 治愈、励志、生活、爱情,覆盖不同心境
  2. 真实性 —— 有出处的名言比"网传"更有力量
  3. 适度性 —— 不要过于沉重,也不要过于轻浮
  4. 时代感 —— 经典名言 + 当代金句,老中青都能找到共鸣
  5. 语言美感 —— 读起来有节奏感、有意象、有余韵

比如"生活最好的状态,是冷冷清清的风风火火"——它用矛盾修辞(冷冷清清 vs 风风火火)制造出张力,让人过目不忘。这就是语言的美感。

又如"愿你历尽千帆,归来仍是少年"——它用"千帆"(经历)和"少年"(初心)形成对比,表达了对初心不改的美好祝愿。一句话里有故事、有画面、有情感。

选名言看似简单,实则考验的是对人性的理解——哪些话能戳中人心,哪些话只是漂亮话。这个能力,算法学不来,只有人能做到。


九、性能优化与最佳实践

9.1 数组操作性能

名言列表的常见操作和性能:

操作 时间复杂度 内存开销 备注
随机选取 O(1) 极小 核心操作,必须快
添加一条 O(1) push 或 spread
删除一条 O(n) 中等 需要遍历查找+删除
搜索 O(n) 无索引时需遍历
排序 O(n log n) 中等 如需按分类排序

对于名言列表这种数据量(通常 < 10000 条),所有操作都是毫秒级的,不需要特殊优化。

但如果列表增长到数万级别(比如从网络API批量导入),就需要考虑:

// 使用 ArrayBuffer 或 TypedArray 存储
// 使用虚拟列表渲染(LazyForEach)
LazyForEach(this.quoteDataSource, (quote: string, index: number) => {
  Text(quote)
    .fontSize(20)
}, (quote: string) => quote)

LazyForEach 只渲染当前可见区域的项,滚动时动态回收和创建。对于超长列表,这是唯一正确的渲染方式。

9.2 避免重复渲染

当前实现中,每次点击按钮只修改 currentIndex。但 currentIndex 的变化会触发整个 build() 方法重新执行吗?

答案是:不会。ArkTS 的 @State 追踪的是读取了该状态的 UI 节点

所以实际上,每次点击只有 Text 组件及其属性会重新渲染。Column、Button、背景全部跳过——这就是细粒度响应式的性能优势。

9.3 代码组织最佳实践

随着功能增加,应该将代码拆分为独立的文件:

pages/
  QuoteGenerator.ets     ← 主页面,组装各组件
components/
  QuoteText.ets          ← 名言文本组件(含动画)
  CategorySelector.ets   ← 分类选择器
  ActionButtons.ets      ← 操作按钮(换一句、收藏、分享)
models/
  QuoteData.ets          ← 数据定义和接口
data/
  quotes.json            ← 名言数据(可网络更新)
utils/
  RandomUtils.ets        ← 随机算法工具
  StorageUtils.ets       ← 本地存储工具

每个文件职责单一,易于测试和维护。


十、总结

10.1 核心要点回顾

这篇文章从一个简单的名言生成器出发,覆盖了以下知识点:

  1. 数据层 —— @State 数组的定义和管理,数据扩展策略
  2. 算法层 —— do...while 防重复随机算法,Fisher-Yates 洗牌算法
  3. UI 层 —— 渐变背景、胶囊按钮、文字排版、间距系统
  4. 设计理念 —— 情绪价值、功能减法、治愈感设计
  5. 扩展方向 —— 动画、分类、网络、收藏、分享、自动轮播
  6. 性能优化 —— 细粒度渲染、数组操作复杂度、LazyForEach
  7. 代码组织 —— 组件拆分、职责划分、目录结构

10.2 代码之美

最后,让我们再读一遍最开始的 57 行代码:

@Entry
@Component
struct QuoteGenerator {
  @State quoteList: string[] = [
    "生活最好的状态,是冷冷清清的风风火火。",
    "心有山海,静而不争。",
    "慢慢来,好戏都在烟火里。",
    "你若盛开,清风自来。",
    "保持热爱,奔赴山海。",
    "往事不回头,未来不将就。",
    "心向阳光,何惧风霜。",
    "平凡日子,也藏着温柔的光。",
    "愿你历尽千帆,归来仍是少年。",
    "人生没有白走的路,每一步都算数。"
  ];
  @State currentIndex: number = 0;

  changeQuote() {
    let newIndex: number;
    do {
      newIndex = Math.floor(Math.random() * this.quoteList.length);
    } while (newIndex === this.currentIndex);
    this.currentIndex = newIndex;
  }

  build() {
    Column({ space: 40 }) {
      Text(this.quoteList[this.currentIndex])
        .fontSize(22)
        .fontWeight(FontWeight.Medium)
        .lineHeight(35)
        .letterSpacing(1)
        .textAlign(TextAlign.Center)
        .padding(20)

      Button("换一句")
        .fontSize(18)
        .width(160)
        .height(50)
        .borderRadius(25)
        .backgroundColor("#FF7A85")
        .fontColor(Color.White)
        .onClick(() => {
          this.changeQuote();
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .linearGradient({
      direction: GradientDirection.Bottom,
      colors: [[0xFFF5F7FA, 0.0], [0xFFE4EAF5, 1.0]]
    })
    .padding(30)
  }
}

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐