HarmonyOS NEXT 实战:沉浸式光感效果古诗文解析模板(状态管理 V2)
适用版本:HarmonyOS NEXT(API 23+)
开发工具:DevEco Studio 6.1+
核心技术:状态管理 V2、沉浸式光感UI、bindPopup 气泡注释、@ObservedV2 细粒度响应
关键词:ArkUI、ComponentV2、ObservedV2、光感效果、古诗文解析
效果

一、项目概述
本项目实现了一个沉浸式光感效果的古诗文解析模板,以唐代诗人王之涣的名作《登鹳雀楼》为示例。用户可以点击诗名、作者、带注释的词语,弹出对应的注释气泡,深入了解古诗文的内涵。
1.1 功能特性
- 沉浸式深色背景:多层渐变 + 动态光斑营造深邃意境
- 毛玻璃卡片:
backdropBlur实现磨砂玻璃质感 - 状态管理 V2:使用
@ComponentV2、@ObservedV2、@Trace、@Local等现代装饰器 - 交互式注释气泡:点击词语弹出注释,支持互斥显示
- 渐变装饰线:标题、词语下方使用线性渐变装饰线
- 流畅动画:光斑持续漂移,增强视觉沉浸感
1.2 技术架构
Index.ets
├── V2 数据模型层
│ ├── PoemSegment ← 诗文片段(@ObservedV2 + @Trace)
│ ├── PoemLine ← 诗句行
│ ├── PoemMeta ← 诗文元数据
│ └── PoemData ← 诗文完整数据
├── 数据工厂函数
│ └── createPoemData() ← 构建示例诗文数据
└── UI 视图层(@ComponentV2)
├── HeaderView() ← 顶部导航栏
├── PoetryCard() ← 诗文卡片(毛玻璃)
│ ├── TitleSection() ← 标题区域
│ ├── AuthorSection() ← 作者区域
│ ├── PoetryContent() ← 诗句内容
│ └── TranslationSection() ← 诗意赏析
└── AnnotationPopup() ← 注释弹框内容
二、设计理念
2.1 为什么选择沉浸式光感效果?
古诗文是中国传统文化的瑰宝,阅读古诗讲究「意境」。传统的白底黑字界面虽然简洁,但缺少氛围感。本方案通过以下设计营造阅读氛围:
| 设计元素 | 视觉效果 | 对应意境 |
|---|---|---|
| 深紫渐变底色 | 深邃夜空感 | 登高望远、意境深远 |
| 暖色光斑 | 烛光/月光感 | 白日依山尽 |
| 金色注释文字 | 高亮标注感 | 重点注释一目了然 |
| 毛玻璃卡片 | 朦胧透视感 | 诗意朦胧、若隐若现 |
| 渐变装饰线 | 现代设计感 | 古典与现代的融合 |
2.2 为什么使用状态管理 V2?
相比 V1 的 @State/@Component,V2 提供了更精确的响应式控制:
| V1 装饰器 | V2 替代 | 优势 |
|---|---|---|
@Component | @ComponentV2 | 更现代的组件模型 |
@State | @Local | 组件内部响应式状态 |
@Observed | @ObservedV2 + @Trace | 精确到属性级的变化追踪 |
@Prop | @Param | 父→子单向数据流 |
V2 的 @Trace 装饰器可以精确追踪每个属性的变化,只有被修改的 @Trace 属性才会触发关联 UI 的更新,避免了不必要的重渲染。
三、数据模型设计
3.1 @ObservedV2 数据类
数据模型采用 V2 的 @ObservedV2 装饰器,配合 @Trace 实现细粒度响应式更新:
/**
* 诗文内容片段
* @Trace 标记的属性变化时,仅更新引用了该属性的 UI 组件
*/
@ObservedV2
class PoemSegment {
@Trace text: string = '' // 文本内容
@Trace hasAnnotation: boolean = false // 是否有注释
@Trace annotation: string = '' // 注释内容
@Trace display: boolean = false // 气泡是否显示
@Trace underlineWidth: number = 0 // 下划线宽度系数
}
/**
* 诗句行 - 每行由多个片段组成
*/
@ObservedV2
class PoemLine {
@Trace segments: PoemSegment[] = []
}
/**
* 诗文元数据 - 标题、作者等
*/
@ObservedV2
class PoemMeta {
@Trace title: string = ''
@Trace titleAnnotation: string = ''
@Trace titleDisplay: boolean = false
@Trace author: string = ''
@Trace dynasty: string = ''
@Trace authorAnnotation: string = ''
@Trace authorDisplay: boolean = false
@Trace translation: string = ''
}
为什么用 @ObservedV2 + @Trace 而不是 V1 的 @Observed?
V1 的 @Observed 会追踪整个对象的变化,即使只修改了一个属性,也可能触发整个组件树的重渲染。而 V2 的 @ObservedV2 + @Trace 可以精确到属性级别——例如修改某个 PoemSegment 的 display 属性时,只有绑定了该属性的 Popup 会响应,其他诗句的渲染不受影响。
3.2 数据工厂函数
诗文数据通过工厂函数构建,将数据创建与 UI 逻辑分离。由于 ArkTS 禁止嵌套函数和未类型化对象字面量,辅助函数 makeSeg、makeLine 必须定义在文件顶层:
// 辅助函数定义在文件顶层(ArkTS 禁止嵌套函数)
function makeSeg(text: string, annotation: string, hasAnnotation: boolean, underlineWidth: number): PoemSegment {
const seg = new PoemSegment()
seg.text = text
seg.annotation = annotation
seg.hasAnnotation = hasAnnotation
seg.underlineWidth = underlineWidth
return seg
}
function makeLine(segments: PoemSegment[]): PoemLine {
const line = new PoemLine()
line.segments = segments
return line
}
function createPoemData(): PoemData {
const poem = new PoemData()
poem.meta.title = '鹳雀楼'
poem.meta.author = '王之涣'
poem.meta.dynasty = '唐代'
// ...
// 直接通过辅助函数构建诗句
const line1 = makeLine([
makeSeg('白日', '白日:太阳。依:依傍。', true, 3),
makeSeg('依', '', false, 0),
makeSeg('山尽', '尽:消失,指太阳落山。', true, 3),
makeSeg(',', '', false, 0)
])
// ... 其他行
poem.lines = [line1, line2, line3, line4]
return poem
}
四、核心UI实现
4.1 沉浸式光感背景
背景由三层元素叠加构成:
Stack() {
// 第一层:深紫渐变底色
Column()
.width('100%')
.height('100%')
.linearGradient({
direction: GradientDirection.RightTop,
colors: [['#0f0c29', 0.0], ['#302b63', 0.45], ['#24243e', 1.0]]
})
// 第二层:暖色光斑(带漂移动画)
Circle()
.width(320).height(320)
.fill('rgba(255, 170, 90, 0.10)')
.blur(80)
.position({ x: -40 + this.lightOffset1, y: 120 + this.lightOffset2 })
// 第三层:冷色光斑
Circle()
.width(280).height(280)
.fill('rgba(110, 160, 255, 0.10)')
.blur(80)
.position({ x: 200 + this.lightOffset2, y: 380 + this.lightOffset1 })
}
光斑动画使用 animateTo 实现持续漂移:
startLightAnimation(): void {
const ctx = this.getUIContext()
// 第一组动画:无限循环
ctx.animateTo({ duration: 7000, iterations: -1, curve: Curve.EaseInOut }, () => {
this.lightOffset1 = 80
this.lightOffset2 = -60
})
// 第二组动画:延迟3.5秒启动,形成交错运动
ctx.animateTo({ delay: 3500, duration: 7000, iterations: -1, curve: Curve.EaseInOut }, () => {
this.lightOffset1 = -40
this.lightOffset2 = 90
})
}
关键参数:
iterations: -1表示无限循环播放,两组动画交错启动产生自然的漂移感。
4.2 毛玻璃诗文卡片
诗文卡片使用 backdropBlur 实现毛玻璃效果:
Column() {
// ... 诗文内容
}
.width('92%')
.padding({ left: 28, right: 28, top: 36, bottom: 36 })
.borderRadius(28)
.backgroundColor('rgba(255,255,255,0.04)') // 极低透明度
.backdropBlur(30) // 毛玻璃模糊
.border({ width: 1, color: 'rgba(255,255,255,0.06)' }) // 微妙边框
.shadow({ radius: 40, color: 'rgba(0,0,0,0.25)', offsetX: 0, offsetY: 12 })
效果解读:
backgroundColor设为极低透明度的白色,让背景光斑若隐若现backdropBlur(30)对卡片下方的内容进行模糊处理,参数为模糊半径- 微妙的边框和阴影增强层次感
4.3 交互式注释气泡
使用 bindPopup + CustomPopupOptions 实现点击词语弹出注释,配合 autoCancel 自动关闭和 closeAllPopups 互斥显示:
Text(seg.text)
.fontSize(20)
.fontColor(seg.hasAnnotation ? '#FFD54F' : 'rgba(255,255,255,0.88)')
.onClick(() => {
if (seg.hasAnnotation) {
this.closeAllPopups() // 先关闭所有气泡
seg.display = !seg.display // 切换当前气泡
}
})
.bindPopup(seg.display, {
builder: (): void => this.AnnotationPopup(seg.annotation),
mask: false,
popupColor: '#FFF8E1', // 暖黄色背景,配合深色文字清晰可读
radius: 16,
enableArrow: true,
autoCancel: true // 点击外部自动关闭
} as CustomPopupOptions)
互斥显示的核心逻辑——closeAllPopups 方法:
closeAllPopups(): void {
this.poem.meta.titleDisplay = false
this.poem.meta.authorDisplay = false
this.poem.lines.forEach((line: PoemLine) => {
line.segments.forEach((seg: PoemSegment) => {
seg.display = false
})
})
}
4.4 渐变装饰线
标题和注释词语下方的装饰线使用线性渐变,增添现代设计感:
// 标题装饰线 - 三色渐变
Row()
.width(64).height(3).borderRadius(2)
.linearGradient({
direction: GradientDirection.Right,
colors: [['#FF8A50', 0.0], ['#FF6B9D', 0.5], ['#B388FF', 1.0]]
})
// 词语下划线 - 双色渐变
Row()
.width(seg.underlineWidth * 16).height(2).borderRadius(1)
.linearGradient({
direction: GradientDirection.Right,
colors: [['#FF8A50', 0.0], ['#FFD54F', 1.0]]
})
4.5 @Builder 复用
通过 @Builder 装饰器将 UI 片段拆分为可复用的方法,保持 build() 方法简洁:
| @Builder 方法 | 职责 | 复用场景 |
|---|---|---|
HeaderView() | 顶部导航栏 | 页面顶部 |
PoetryCard() | 诗文卡片容器 | 卡片布局 |
TitleSection() | 标题区域 | 诗名展示+注释 |
AuthorSection() | 作者区域 | 朝代标签+作者+注释 |
PoetryContent() | 诗句区域 | 逐行逐词渲染 |
TranslationSection() | 诗意赏析 | 底部翻译展示 |
AnnotationPopup() | 注释弹框内容 | 所有注释气泡共用 |
五、实现步骤详解
步骤一:创建项目
- 打开 DevEco Studio,创建一个新的 HarmonyOS 项目
- 选择 Empty Ability 模板,API 版本选择 12+
- 项目创建完成后,确认
build-profile.json5中的编译版本
步骤二:设计数据模型
在 src/main/ets/pages/Index.ets 中定义 V2 数据模型类:
@ObservedV2
class PoemSegment {
@Trace text: string = ''
@Trace hasAnnotation: boolean = false
@Trace annotation: string = ''
@Trace display: boolean = false
@Trace underlineWidth: number = 0
}
要点:每个
@Trace属性都会被框架精确追踪,修改display不会影响text的渲染。
步骤三:构建诗文数据
使用工厂函数创建《登鹳雀楼》的完整数据,包括标题、作者、每句诗的注释标注。
步骤四:搭建页面骨架
@Entry
@ComponentV2
struct Index {
@Local poem: PoemData = createPoemData()
build() {
Stack() {
// 背景层
// 光斑层
// 内容层
Column() {
this.HeaderView()
Scroll() { this.PoetryCard() }
}
}
}
}
步骤五:实现沉浸式光感效果
添加渐变底色 + 动态光斑(详见第四节 4.1)。
步骤六:实现毛玻璃卡片
使用 backdropBlur(30) 实现磨砂玻璃效果。
步骤七:实现交互式注释气泡
为标题、作者、注释词语分别绑定 bindPopup,配合 closeAllPopups 实现互斥显示。
步骤八:添加诗意赏析模块
在卡片底部添加赏析文本,使用分隔线和标题区分层次。
六、V1 → V2 迁移对照表
对于已有 V1 代码的开发者,以下是常用的迁移映射:
| V1 写法 | V2 写法 | 说明 |
|---|---|---|
@Component | @ComponentV2 | 组件装饰器 |
@State title: string = '' | @Local title: string = '' | 组件内部状态 |
@Observed class Model | @ObservedV2 class Model | 数据类观察 |
name: string (普通属性) | @Trace name: string | 精确属性追踪 |
@Prop item: Model | @Param item: Model | 父→子数据传递 |
@Watch('onChange') | @Monitor('prop') | 属性变化监听 |
@Provide('key') | @Provider() prop | 跨组件状态提供 |
@Consume('key') | @Consumer() prop | 跨组件状态消费 |
七、性能优化要点
7.1 @Trace 精确追踪
使用 @Trace 而非整个对象重赋值,减少不必要的重渲染:
// ✅ 好 - 只更新 display 相关的 UI
seg.display = true
// ❌ 避免 - 整个对象重赋值触发全量更新
line.segments = [...line.segments]
7.2 避免 build() 中的副作用
build() 方法应保持纯净,不在其中执行网络请求、日志打印等副作用操作:
// ✅ 好 - build() 只做 UI 描述
build() {
Text(this.poem.meta.title)
}
// ❌ 避免 - build() 中有副作用
build() {
console.info('rendering...')
Text(this.poem.meta.title)
}
7.3 @Builder 复用减少重复代码
将注释弹框的 UI 抽取为 @Builder 方法,所有气泡共用同一个 Builder:
@Builder
AnnotationPopup(text: string) {
Column() {
Text(text).fontSize(13).fontColor('rgba(255,255,255,0.88)').lineHeight(22)
}
.padding(16)
.width(220)
}
八、扩展建议
8.1 支持多首诗词
将诗文数据抽取为数组,支持左右滑动切换:
@Local poems: PoemData[] = [poem1, poem2, poem3]
@Local currentIndex: number = 0
Swiper() {
ForEach(this.poems, (poem: PoemData) => {
this.PoetryCard()
})
}
8.2 适配深色/浅色模式
通过资源限定符(resources/base 和 resources/dark)实现主题自适应:
.backgroundColor($r('app.color.card_bg')) // 自动适配深浅色模式
.fontColor($r('app.color.text_primary')) // 自动适配
8.3 添加朗读功能
集成 TTS(文本转语音)能力,让用户可以听诗词朗读:
import { textToSpeech } from '@kit.CoreSpeechKit'
// 朗读诗句
textToSpeech.createEngine().speak(this.poem.lines[0].segments.map(s => s.text).join(''))
九、总结
本案例展示了如何使用 HarmonyOS NEXT 的状态管理 V2 和现代 UI 能力,构建一个具有沉浸式视觉体验的古诗文解析模板。核心收获:
| 技术点 | 核心价值 |
|---|---|
@ComponentV2 + @Local | 现代组件模型,更简洁的状态管理 |
@ObservedV2 + @Trace | 精确到属性级的响应式更新 |
bindPopup + CustomPopupOptions | 灵活的交互式注释气泡 |
linearGradient + blur | 沉浸式光感背景效果 |
backdropBlur | 毛玻璃卡片质感 |
@Builder | UI 片段复用,保持代码整洁 |
animateTo + iterations: -1 | 持续光斑漂移循环动画 |
通过本模板,开发者可以快速搭建类似的诗词赏析、经典阅读类应用,为用户提供美观且富有交互性的阅读体验。
‘’))
## 九、总结
本案例展示了如何使用 HarmonyOS NEXT 的状态管理 V2 和现代 UI 能力,构建一个具有沉浸式视觉体验的古诗文解析模板。核心收获:
| 技术点 | 核心价值 |
|--------|----------|
| `@ComponentV2` + `@Local` | 现代组件模型,更简洁的状态管理 |
| `@ObservedV2` + `@Trace` | 精确到属性级的响应式更新 |
| `bindPopup` + `CustomPopupOptions` | 灵活的交互式注释气泡 |
| `linearGradient` + `blur` | 沉浸式光感背景效果 |
| `backdropBlur` | 毛玻璃卡片质感 |
| `@Builder` | UI 片段复用,保持代码整洁 |
| `animateTo` + `iterations: -1` | 持续光斑漂移循环动画 |
通过本模板,开发者可以快速搭建类似的诗词赏析、经典阅读类应用,为用户提供美观且富有交互性的阅读体验。

216

被折叠的 条评论
为什么被折叠?



