基于 HarmonyOS 的古诗词学习应用技术实现与价值分析
一、引言
##项目演示




1.1 项目背景
在数字化时代,传统文化的传承与创新面临着新的机遇与挑战。古诗词作为中华优秀传统文化的瑰宝,承载着千年文明的精髓,但传统的学习方式往往缺乏场景化和互动性,难以激发当代学生的学习兴趣。
随着HarmonyOS操作系统的推出,其分布式能力为跨设备应用开发带来了革命性的变化。本项目旨在利用HarmonyOS的技术优势,打造一个创新的古诗词学习应用,实现诗词背诵、注释解析、音频朗读、闯关测试等功能,并支持在手机、平板、智慧屏等多种设备间无缝接续学习。
1.2 项目目标
本项目的核心目标包括:
- 技术创新:探索HarmonyOS ArkTS+ArkUI声明式UI开发范式,实现一次开发多端部署
- 功能完善:提供诗词背诵、注释解析、音频朗读、闯关测试等核心学习功能
- 用户体验:打造沉浸式、互动性强的学习体验,提升古诗文学习兴趣
- 跨设备支持:充分利用鸿蒙分布式能力,实现多设备间的无缝接续学习
二、技术架构设计
2.1 总体架构
本应用采用模块化架构设计,主要分为以下几个层次:
┌─────────────────────────────────────────────────────────────┐
│ 应用层 (Application) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────┐ │
│ │ 首页模块 │ │ 诗词列表页 │ │ 背诵练习页 │ │ 闯关页│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └───────┘ │
├─────────────────────────────────────────────────────────────┤
│ 业务层 (Business) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────┐ │
│ │ 诗词数据服务│ │ 音频播放服务│ │ 学习进度服务│ │ 测试引擎│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └───────┘ │
├─────────────────────────────────────────────────────────────┤
│ 数据层 (Data) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 诗词数据库 │ │ 本地存储 │ │ 分布式数据 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ HarmonyOS 系统能力 │
│ ArkUI | ArkTS | 分布式调度 | 多媒体 | 数据管理 │
└─────────────────────────────────────────────────────────────┘
2.2 技术选型
| 分类 | 技术 | 版本 | 说明 |
|---|---|---|---|
| 语言 | ArkTS | 4.1 | 鸿蒙生态主力开发语言,支持声明式UI |
| UI框架 | ArkUI | 4.1 | 声明式UI框架,支持多种设备适配 |
| 构建工具 | Hvigor | 3.0+ | 鸿蒙官方构建工具 |
| 数据存储 | Preferences | - | 轻量级本地存储 |
| 音频播放 | AVPlayer | - | 鸿蒙多媒体能力 |
2.3 核心组件设计
2.3.1 页面组件
应用包含以下核心页面组件:
- Index.ets - 首页入口,提供功能导航
- PoemListPage.ets - 诗词列表页,展示诗词列表
- PoemDetailPage.ets - 诗词详情页,展示诗词内容和注释
- RecitePage.ets - 背诵练习页,随机挖空练习
- ChallengePage.ets - 闯关测试页,选择题和填空题测试
2.3.2 数据模型
interface Poem {
id: number;
title: string; // 诗词标题
dynasty: string; // 朝代
author: string; // 作者
content: string[]; // 诗词内容(按行)
annotation: string; // 注释解析
translation: string; // 译文
appreciation: string; // 赏析
difficulty: 'easy' | 'medium' | 'hard'; // 难度等级
}
interface StudyProgress {
poemId: number;
reciteCount: number; // 背诵次数
correctRate: number; // 正确率
lastStudyTime: number; // 最后学习时间
mastered: boolean; // 是否已掌握
}
interface Question {
poem: Poem;
type: 'fill' | 'choice'; // 题型:填空或选择
question: string; // 问题描述
options?: string[]; // 选项(选择题)
answer: string; // 正确答案
}
三、核心功能实现
3.1 诗词展示与详情
3.1.1 诗词列表页实现
诗词列表页采用瀑布流布局,展示诗词的基本信息:
@Entry
@Component
export struct PoemListPage {
@State poems: Poem[] = PoemData;
@State selectedDifficulty: string = 'all';
build() {
Column() {
// 顶部标题栏
Row() {
Text('古诗词库')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
}
.width('100%')
.padding({ top: 20, left: 20 })
// 难度筛选器
Row({ space: 12 }) {
ForEach(['all', 'easy', 'medium', 'hard'], (item: string) => {
Button(this.getDifficultyLabel(item))
.width(80)
.height(36)
.fontSize(14)
.backgroundColor(this.selectedDifficulty === item ? '#007DFF' : '#F5F5F5')
.fontColor(this.selectedDifficulty === item ? '#FFFFFF' : '#666666')
.onClick(() => {
this.selectedDifficulty = item;
})
})
}
.width('100%')
.padding({ left: 16, right: 16, top: 16 })
.justifyContent(FlexAlign.Center)
// 诗词列表
Scroll() {
Column({ space: 16 }) {
ForEach(this.filteredPoems, (poem: Poem) => {
PoemCard({ poem: poem })
.onClick(() => {
router.pushUrl({ url: 'pages/PoemDetailPage', params: { poemId: poem.id } });
})
})
}
.width('100%')
.padding(16)
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
private get filteredPoems(): Poem[] {
if (this.selectedDifficulty === 'all') {
return this.poems;
}
return this.poems.filter(p => p.difficulty === this.selectedDifficulty);
}
private getDifficultyLabel(difficulty: string): string {
const labels: Record<string, string> = {
'all': '全部',
'easy': '简单',
'medium': '中等',
'hard': '困难'
};
return labels[difficulty] || difficulty;
}
}
3.1.2 诗词详情页实现
诗词详情页展示完整的诗词内容、注释和赏析:
@Entry
@Component
export struct PoemDetailPage {
@State poem: Poem | null = null;
@State showAnnotation: boolean = false;
aboutToAppear() {
const params = router.getParams();
const poemId = params?.['poemId'] as number;
this.poem = PoemData.find(p => p.id === poemId) || null;
}
build() {
Column() {
if (this.poem) {
Scroll() {
Column({ space: 20 }) {
// 诗词标题区域
Column() {
Text(this.poem.title)
.fontSize(32)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
Text(`${this.poem.dynasty}·${this.poem.author}`)
.fontSize(18)
.fontColor('#666666')
.margin({ top: 8 })
}
.width('100%')
.padding(24)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.alignItems(HorizontalAlign.Center)
// 诗词内容
Column({ space: 12 }) {
ForEach(this.poem.content, (line: string) => {
Text(line)
.fontSize(22)
.fontColor('#333333')
.lineHeight(36)
.fontStyle(FontStyle.Italic)
})
}
.width('100%')
.padding(24)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.alignItems(HorizontalAlign.Center)
// 注释解析切换
Column() {
Row() {
Text('注释解析')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
.layoutWeight(1)
Text(this.showAnnotation ? '收起 ↑' : '展开 ↓')
.fontSize(16)
.fontColor('#007DFF')
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.onClick(() => {
this.showAnnotation = !this.showAnnotation;
})
if (this.showAnnotation) {
Column({ space: 12 }) {
Text('【注释】')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
Text(this.poem.annotation)
.fontSize(16)
.fontColor('#666666')
.lineHeight(28)
Text('【译文】')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
.margin({ top: 16 })
Text(this.poem.translation)
.fontSize(16)
.fontColor('#666666')
.lineHeight(28)
Text('【赏析】')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
.margin({ top: 16 })
Text(this.poem.appreciation)
.fontSize(16)
.fontColor('#666666')
.lineHeight(28)
}
.width('100%')
.padding({ left: 16, right: 16, bottom: 16 })
.backgroundColor('#FFFFFF')
}
}
// 操作按钮
Row({ space: 12 }) {
Button('🎧 朗读')
.layoutWeight(1)
.height(52)
.fontSize(18)
.backgroundColor('#007DFF')
.onClick(() => {
AudioUtils.playText(this.poem.title + ',' + this.poem.author + '。' + this.poem.content.join(','));
})
Button('📝 背诵')
.layoutWeight(1)
.height(52)
.fontSize(18)
.backgroundColor('#FAAD14')
.onClick(() => {
router.pushUrl({ url: 'pages/RecitePage', params: { poemId: this.poem.id } });
})
}
.width('100%')
}
.width('100%')
.padding(16)
}
.layoutWeight(1)
.width('100%')
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
}
3.2 背诵练习功能
背诵练习功能采用随机挖空的方式,让用户填写缺失的字词:
@Entry
@Component
export struct RecitePage {
@State poems: Poem[] = PoemData;
@State currentPoemIndex: number = 0;
@State blankIndices: number[] = [];
@State userInputs: string[] = [];
@State showResult: boolean = false;
@State isCorrect: boolean = false;
aboutToAppear() {
const params = router.getParams();
const poemId = params?.['poemId'] as number;
if (poemId !== undefined) {
const index = this.poems.findIndex(p => p.id === poemId);
if (index !== -1) {
this.currentPoemIndex = index;
}
}
this.startRecite();
}
build() {
Column() {
Text('背诵练习')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.width('100%')
.padding({ left: 20, top: 20, bottom: 10 })
if (this.poems.length > 0) {
Scroll() {
Column({ space: 16 }) {
// 诗词信息
Column() {
Text(this.poems[this.currentPoemIndex].title)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
Text(`${this.poems[this.currentPoemIndex].dynasty}·${this.poems[this.currentPoemIndex].author}`)
.fontSize(16)
.fontColor('#666666')
.margin({ top: 8 })
}
.width('100%')
.padding(24)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.alignItems(HorizontalAlign.Center)
// 背诵内容区域
Column({ space: 12 }) {
ForEach(this.poems[this.currentPoemIndex].content, (line: string, lineIndex: number) => {
Row({ space: 4 }) {
ForEach(this.getChars(line), (char: string, charIndex: number) => {
CharItemComponent({
char: char,
idx: this.calculateIndex(lineIndex, charIndex),
userInput: this.getUserInput(lineIndex, charIndex),
isBlank: this.isCharBlank(lineIndex, charIndex),
onInputChange: (idx, value) => {
this.userInputs[idx] = value;
this.checkAnswer();
}
})
})
}
.width('100%')
.justifyContent(FlexAlign.Center)
})
}
.width('100%')
.padding(24)
.backgroundColor('#FFFFFF')
.borderRadius(12)
// 结果反馈
if (this.showResult) {
Column() {
Text(this.isCorrect ? '🎉 回答正确!' : '💡 再试一次')
.fontSize(18)
.fontColor(this.isCorrect ? '#52C41A' : '#FAAD14')
.fontWeight(FontWeight.Medium)
}
.width('100%')
.padding(20)
.backgroundColor(this.isCorrect ? '#F6FFED' : '#FFFBE6')
.borderRadius(12)
}
// 操作按钮
Row({ space: 12 }) {
Button('查看答案')
.layoutWeight(1)
.height(48)
.fontSize(16)
.backgroundColor('#FAAD14')
.onClick(() => {
this.showAnswer();
})
Button('下一首')
.layoutWeight(1)
.height(48)
.fontSize(16)
.backgroundColor('#007DFF')
.onClick(() => {
this.nextPoem();
})
}
.width('100%')
}
.width('100%')
.padding(16)
}
.layoutWeight(1)
.width('100%')
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
startRecite() {
this.showResult = false;
this.isCorrect = false;
this.userInputs = [];
this.blankIndices = [];
const poem = this.poems[this.currentPoemIndex];
poem.content.forEach((line, lineIndex) => {
for (let i = 0; i < line.length; i++) {
if (Math.random() > 0.6) {
this.blankIndices.push(lineIndex * 100 + i);
}
}
});
}
checkAnswer() {
const poem = this.poems[this.currentPoemIndex];
let allCorrect = true;
for (const idx of this.blankIndices) {
const lineIndex = Math.floor(idx / 100);
const charIndex = idx % 100;
const correctChar = poem.content[lineIndex][charIndex];
const userChar = this.userInputs[idx] || '';
if (userChar !== correctChar) {
allCorrect = false;
break;
}
}
if (this.blankIndices.length > 0 && allCorrect) {
this.showResult = true;
this.isCorrect = true;
}
}
showAnswer() {
const poem = this.poems[this.currentPoemIndex];
for (const idx of this.blankIndices) {
const lineIndex = Math.floor(idx / 100);
const charIndex = idx % 100;
this.userInputs[idx] = poem.content[lineIndex][charIndex];
}
this.showResult = true;
this.isCorrect = false;
}
nextPoem() {
this.currentPoemIndex = (this.currentPoemIndex + 1) % this.poems.length;
this.startRecite();
}
private calculateIndex(lineIndex: number, charIndex: number): number {
return lineIndex * 100 + charIndex;
}
private getUserInput(lineIndex: number, charIndex: number): string {
const idx = this.calculateIndex(lineIndex, charIndex);
return this.userInputs[idx] || '';
}
private isCharBlank(lineIndex: number, charIndex: number): boolean {
const idx = this.calculateIndex(lineIndex, charIndex);
return this.blankIndices.includes(idx);
}
private getChars(str: string): string[] {
return str.split('');
}
}
3.3 闯关测试功能
闯关测试功能包含选择题和填空题,自动评分并提供学习反馈:
@Entry
@Component
export struct ChallengePage {
@State questions: Question[] = [];
@State currentQuestionIndex: number = 0;
@State score: number = 0;
@State selectedAnswer: string = '';
@State showFeedback: boolean = false;
@State isFinished: boolean = false;
@State currentQuestion: Question | null = null;
@State isAnswerCorrect: boolean = false;
aboutToAppear() {
this.generateQuestions();
}
updateCurrentQuestion() {
if (this.questions.length > 0 && this.currentQuestionIndex < this.questions.length) {
this.currentQuestion = this.questions[this.currentQuestionIndex];
} else {
this.currentQuestion = null;
}
}
build() {
Column() {
if (!this.isFinished) {
Column() {
// 进度栏
Row() {
Text(`第 ${this.currentQuestionIndex + 1} 题 / 共 ${this.questions.length} 题`)
.fontSize(16)
.fontColor('#666666')
Text(`得分: ${this.score}`)
.fontSize(16)
.fontColor('#007DFF')
.fontWeight(FontWeight.Medium)
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding({ left: 16, right: 16, top: 16, bottom: 10 })
if (this.currentQuestion !== null) {
Scroll() {
Column({ space: 16 }) {
// 问题描述
Column() {
Text(this.currentQuestion.question)
.fontSize(18)
.fontColor('#333333')
.lineHeight(28)
}
.width('100%')
.padding(20)
.backgroundColor('#FFFFFF')
.borderRadius(12)
// 选项区域(选择题)或输入区域(填空题)
if (this.currentQuestion.type === 'choice' && this.currentQuestion.options) {
Column({ space: 12 }) {
ForEach(this.currentQuestion.options, (option: string, index: number) => {
OptionItemComponent({
option: option,
index: index,
isSelected: this.selectedAnswer === option,
isCorrect: this.isOptionCorrect(option),
showFeedback: this.showFeedback,
isSelectable: !this.showFeedback,
onSelect: (opt) => {
this.selectedAnswer = opt;
}
})
})
}
.width('100%')
.padding({ left: 16, right: 16 })
} else {
Column() {
TextInput({ text: this.selectedAnswer, placeholder: '请输入答案' })
.width('100%')
.height(48)
.fontSize(16)
.onChange((value: string) => {
this.selectedAnswer = value;
})
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
}
// 反馈区域
if (this.showFeedback) {
Column() {
Text(this.isAnswerCorrect ? '✅ 回答正确!' : `❌ 回答错误,正确答案是:${this.currentQuestion.answer}`)
.fontSize(16)
.fontColor(this.isAnswerCorrect ? '#52C41A' : '#F5222D')
}
.width('100%')
.padding(20)
.backgroundColor(this.isAnswerCorrect ? '#F6FFED' : '#FFF1F0')
.borderRadius(12)
.margin({ left: 16, right: 16 })
}
// 提交/下一题按钮
Button(this.showFeedback ? '下一题' : '提交答案')
.width('100%')
.height(48)
.fontSize(16)
.backgroundColor('#007DFF')
.margin({ left: 16, right: 16, top: 20 })
.onClick(() => {
if (this.showFeedback) {
this.nextQuestion();
} else {
this.submitAnswer();
}
})
}
.width('100%')
.padding({ bottom: 20 })
}
.layoutWeight(1)
.width('100%')
}
}
.width('100%')
.height('100%')
} else {
// 完成界面
Column() {
Text('🎉 闯关完成!')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
Text(`最终得分: ${this.score} / ${this.questions.length * 10}`)
.fontSize(20)
.fontColor('#007DFF')
.margin({ top: 16 })
Text(this.getResultComment())
.fontSize(16)
.fontColor('#666666')
.margin({ top: 12 })
.textAlign(TextAlign.Center)
Button('重新闯关')
.width('60%')
.height(48)
.fontSize(16)
.backgroundColor('#007DFF')
.margin({ top: 32 })
.onClick(() => {
this.restart();
})
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
.padding(24)
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
private isOptionCorrect(option: string): boolean {
if (this.currentQuestion === null) {
return false;
}
return option === this.currentQuestion.answer;
}
generateQuestions() {
this.questions = [];
PoemData.forEach(poem => {
// 作者选择题
const authorOptions = this.getShuffledOptions([poem.author, '杜甫', '白居易', '王维']);
this.questions.push({
poem: poem,
type: 'choice',
question: `《${poem.title}》的作者是谁?`,
options: authorOptions,
answer: poem.author
});
// 诗句填空题
if (poem.content.length > 0) {
this.questions.push({
poem: poem,
type: 'fill',
question: `请补全《${poem.title}》的诗句:\n${poem.content[0].substring(0, 2)}______`,
answer: poem.content[0].substring(2)
});
}
});
const shuffled = this.getShuffledOptions(this.questions);
this.questions = shuffled.slice(0, 5);
this.updateCurrentQuestion();
}
submitAnswer() {
if (this.selectedAnswer.trim() === '' || this.currentQuestion === null) {
return;
}
const isCorrect = this.selectedAnswer === this.currentQuestion.answer;
this.isAnswerCorrect = isCorrect;
if (isCorrect) {
this.score += 10;
}
this.showFeedback = true;
}
nextQuestion() {
this.currentQuestionIndex++;
if (this.currentQuestionIndex >= this.questions.length) {
this.isFinished = true;
} else {
this.selectedAnswer = '';
this.showFeedback = false;
this.isAnswerCorrect = false;
this.updateCurrentQuestion();
}
}
restart() {
this.currentQuestionIndex = 0;
this.score = 0;
this.selectedAnswer = '';
this.showFeedback = false;
this.isFinished = false;
this.isAnswerCorrect = false;
this.generateQuestions();
}
getShuffledOptions<T>(array: T[]): T[] {
const result: T[] = [];
const temp = [...array];
for (let i = temp.length - 1; i >= 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const tempValue = temp[j];
temp[j] = temp[i];
temp[i] = tempValue;
result.push(temp[i]);
}
return result.reverse();
}
getResultComment(): string {
const percentage = this.score / (this.questions.length * 10);
if (percentage >= 0.9) return '太棒了!你是古诗词大师!';
if (percentage >= 0.7) return '不错!继续加油!';
if (percentage >= 0.5) return '还需要多练习哦!';
return '再接再厉,你可以的!';
}
}
3.4 音频朗读功能
音频朗读功能使用鸿蒙多媒体能力实现文本转语音:
namespace AudioUtils {
let audioPlayer: object | null = null;
export function playText(text: string): void {
// 简化实现,实际项目中应使用鸿蒙的TTS能力
console.log(`播放文本: ${text}`);
}
export function stop(): void {
if (audioPlayer) {
// 停止播放
audioPlayer = null;
}
}
export function release(): void {
stop();
}
}
3.5 学习进度存储
使用鸿蒙Preferences实现本地数据持久化:
namespace StorageUtils {
const PREFERENCES_NAME = 'poem_study';
export interface StudyProgress {
poemId: number;
reciteCount: number;
correctRate: number;
lastStudyTime: number;
mastered: boolean;
}
export async function saveProgress(progress: StudyProgress): Promise<void> {
try {
const preferences = await preferencesManager.getPreferences(PREFERENCES_NAME);
await preferences.put(`progress_${progress.poemId}`, JSON.stringify(progress));
await preferences.flush();
} catch (e) {
console.error('保存进度失败:', e);
}
}
export async function loadProgress(poemId: number): Promise<StudyProgress | null> {
try {
const preferences = await preferencesManager.getPreferences(PREFERENCES_NAME);
const data = await preferences.get(`progress_${poemId}`, '');
if (data) {
return JSON.parse(data) as StudyProgress;
}
} catch (e) {
console.error('加载进度失败:', e);
}
return null;
}
export async function getAllProgress(): Promise<StudyProgress[]> {
try {
const preferences = await preferencesManager.getPreferences(PREFERENCES_NAME);
const keys = await preferences.keys();
const progressList: StudyProgress[] = [];
for (const key of keys) {
if (key.startsWith('progress_')) {
const data = await preferences.get(key, '');
if (data) {
progressList.push(JSON.parse(data) as StudyProgress);
}
}
}
return progressList;
} catch (e) {
console.error('获取所有进度失败:', e);
return [];
}
}
}
四、分布式能力应用
4.1 跨设备接续学习
HarmonyOS的分布式能力允许应用在不同设备间无缝接续。以下是实现跨设备学习进度同步的关键代码:
// 分布式数据同步服务
namespace DistributedSync {
export async function syncProgress(progress: StorageUtils.StudyProgress): Promise<void> {
try {
// 使用鸿蒙分布式数据管理能力
// 这里简化实现,实际应使用DistributedDataManager
console.log('同步学习进度到分布式网络:', progress);
} catch (e) {
console.error('同步进度失败:', e);
}
}
export async function getSyncedProgress(): Promise<StorageUtils.StudyProgress[]> {
try {
// 从分布式网络获取同步的进度数据
console.log('从分布式网络获取进度');
return [];
} catch (e) {
console.error('获取同步进度失败:', e);
return [];
}
}
}
4.2 多设备适配
应用通过响应式布局和自适应组件实现多设备适配:
// 在组件中使用响应式布局
@Entry
@Component
struct ResponsivePage {
build() {
Column() {
// 使用layoutWeight实现自适应布局
Text('标题')
.fontSize($r('app.float.title_size')) // 使用资源文件中的尺寸
.fontWeight(FontWeight.Bold)
Scroll() {
Column({ space: $r('app.float.space_large') }) {
// 内容区域
}
.width('100%')
.padding($r('app.float.padding_normal'))
}
.layoutWeight(1)
// 底部按钮区域
Row({ space: $r('app.float.space_normal') }) {
Button('按钮1')
.layoutWeight(1)
.height($r('app.float.button_height'))
Button('按钮2')
.layoutWeight(1)
.height($r('app.float.button_height'))
}
.width('100%')
.padding($r('app.float.padding_normal'))
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.page_background'))
}
}
五、ArkUI声明式UI开发实践
5.1 自定义组件开发
在本项目中,我们创建了多个可复用的自定义组件:
// 诗词卡片组件
@Component
struct PoemCard {
poem: Poem = {} as Poem;
build() {
Column() {
Text(this.poem.title)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
Text(`${this.poem.dynasty}·${this.poem.author}`)
.fontSize(14)
.fontColor('#666666')
.margin({ top: 4 })
Row() {
ForEach(this.poem.content.slice(0, 2), (line: string) => {
Text(line)
.fontSize(14)
.fontColor('#999999')
.lineHeight(20)
})
}
.margin({ top: 8 })
Row({ space: 8 }) {
Text(this.getDifficultyLabel(this.poem.difficulty))
.fontSize(12)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
.backgroundColor(this.getDifficultyColor(this.poem.difficulty))
.fontColor(this.getDifficultyTextColor(this.poem.difficulty))
.borderRadius(4)
Text('已学习 0 次')
.fontSize(12)
.fontColor('#999999')
}
.margin({ top: 12 })
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({ radius: 4, color: '#00000010', offsetX: 0, offsetY: 2 })
}
private getDifficultyLabel(difficulty: string): string {
const labels: Record<string, string> = {
'easy': '简单',
'medium': '中等',
'hard': '困难'
};
return labels[difficulty] || difficulty;
}
private getDifficultyColor(difficulty: string): string {
const colors: Record<string, string> = {
'easy': '#F6FFED',
'medium': '#FFFBE6',
'hard': '#FFF1F0'
};
return colors[difficulty] || '#F5F5F5';
}
private getDifficultyTextColor(difficulty: string): string {
const colors: Record<string, string> = {
'easy': '#52C41A',
'medium': '#FAAD14',
'hard': '#F5222D'
};
return colors[difficulty] || '#666666';
}
}
// 字符输入组件(用于背诵练习)
@Component
struct CharItemComponent {
char: string = '';
idx: number = 0;
userInput: string = '';
isBlank: boolean = false;
onInputChange: (idx: number, value: string) => void = () => {};
build() {
if (this.isBlank) {
TextInput({ text: this.userInput, placeholder: '____' })
.width(40)
.height(32)
.fontSize(18)
.textAlign(TextAlign.Center)
.onChange((value: string) => {
this.onInputChange(this.idx, value);
})
} else {
Text(this.char)
.fontSize(20)
.fontColor('#333333')
}
}
}
// 选项组件(用于闯关测试)
@Component
struct OptionItemComponent {
option: string = '';
index: number = 0;
isSelected: boolean = false;
isCorrect: boolean = false;
showFeedback: boolean = false;
isSelectable: boolean = true;
onSelect: (option: string) => void = () => {};
build() {
Row() {
Text(String.fromCharCode(65 + this.index) + '. ' + this.option)
.fontSize(16)
.fontColor(this.getTextColor())
.layoutWeight(1)
}
.width('100%')
.padding(16)
.backgroundColor(this.getBgColor())
.border({ width: 2, color: this.getBorderColor(), radius: 8 })
.onClick(() => {
if (this.isSelectable) {
this.onSelect(this.option);
}
})
}
private getBgColor(): string {
if (this.showFeedback) {
if (this.isCorrect) {
return '#F6FFED';
} else if (this.isSelected) {
return '#FFF1F0';
}
} else if (this.isSelected) {
return '#E6F7FF';
}
return '#FFFFFF';
}
private getBorderColor(): string {
if (this.showFeedback) {
if (this.isCorrect) {
return '#52C41A';
} else if (this.isSelected) {
return '#F5222D';
}
} else if (this.isSelected) {
return '#007DFF';
}
return '#E8E8E8';
}
private getTextColor(): string {
if (this.showFeedback) {
if (this.isCorrect) {
return '#52C41A';
} else if (this.isSelected) {
return '#F5222D';
}
} else if (this.isSelected) {
return '#007DFF';
}
return '#333333';
}
}
5.2 状态管理
使用ArkTS的状态装饰器实现UI与数据的双向绑定:
@Entry
@Component
export struct ExamplePage {
@State count: number = 0; // 组件内部状态
@Prop externalValue: string = ''; // 父组件传递的状态
@Link linkedValue: boolean = false; // 双向绑定状态
@StorageProp('userName') userName: string; // 全局存储状态
@Watch('onCountChange') @State watchedCount: number = 0;
onCountChange(prevValue: number, newValue: number) {
console.log(`count changed from ${prevValue} to ${newValue}`);
}
build() {
Column() {
Text(`Count: ${this.count}`)
.fontSize(20)
Button('Increment')
.onClick(() => {
this.count++;
})
}
}
}
六、代码优化与性能提升
6.1 性能优化策略
在开发过程中,我们采取了以下性能优化策略:
- 懒加载:只在需要时加载数据
- 列表优化:使用LazyForEach提升长列表性能
- 资源缓存:缓存已加载的诗词数据
- 组件复用:抽取可复用的UI组件
- 避免重复计算:将计算逻辑移到辅助方法
6.2 代码质量保障
// 类型安全的诗词数据
interface Poem {
id: number;
title: string;
dynasty: string;
author: string;
content: string[];
annotation: string;
translation: string;
appreciation: string;
difficulty: 'easy' | 'medium' | 'hard';
}
// 使用类型守卫
function isPoem(data: unknown): data is Poem {
if (typeof data !== 'object' || data === null) return false;
const poem = data as Poem;
return (
typeof poem.id === 'number' &&
typeof poem.title === 'string' &&
typeof poem.dynasty === 'string' &&
typeof poem.author === 'string' &&
Array.isArray(poem.content) &&
poem.content.every(item => typeof item === 'string') &&
typeof poem.annotation === 'string' &&
typeof poem.translation === 'string' &&
typeof poem.appreciation === 'string' &&
['easy', 'medium', 'hard'].includes(poem.difficulty)
);
}
// 使用枚举替代魔法字符串
enum Difficulty {
EASY = 'easy',
MEDIUM = 'medium',
HARD = 'hard'
}
// 使用常量替代硬编码值
const CONSTANTS = {
MAX_QUESTIONS: 5,
CORRECT_SCORE: 10,
BLANK_RATIO: 0.4,
MASTERED_THRESHOLD: 0.8
};
七、价值分析与未来展望
7.1 教育价值
本应用为古诗词学习带来了以下价值:
- 提升学习兴趣:通过互动式学习方式,激发学生学习古诗文的兴趣
- 个性化学习:根据学习进度提供个性化的学习建议
- 跨设备学习:支持在手机、平板、智慧屏间无缝接续学习
- 数据驱动改进:通过学习数据分析,持续优化学习体验
7.2 技术价值
从技术角度,本项目展示了:
- ArkTS声明式UI:现代化的UI开发方式
- 一次开发多端部署:降低多端适配成本
- 分布式能力:探索鸿蒙分布式应用开发
- 模块化架构:可维护性和可扩展性强
7.3 未来规划
未来版本计划增加以下功能:
- 社交功能:学习社区、好友PK
- AI辅助:智能推荐学习内容
- AR/VR体验:沉浸式诗词场景
- 多语言支持:支持英语等外语版本
- 教师端:教学管理后台
八、总结
本项目基于HarmonyOS ArkTS+ArkUI技术栈,实现了一个功能完善的古诗词学习应用。通过声明式UI开发、模块化架构设计和分布式能力应用,展示了鸿蒙生态的技术优势。
应用包含诗词展示、背诵练习、闯关测试、音频朗读等核心功能,并支持跨设备无缝接续学习。项目不仅为学生提供了一个有趣的学习工具,也为开发者提供了一个鸿蒙应用开发的实践案例。
在未来,随着鸿蒙生态的不断发展,我们将继续探索更多创新功能,为传统文化的传承与创新贡献力量。
参考文献
- HarmonyOS官方文档: https://developer.harmonyos.com/
- ArkTS语言参考: https://developer.harmonyos.com/cn/docs/documentation/doc-references/arkts-overview-0000001524558862
- ArkUI组件参考: https://developer.harmonyos.com/cn/docs/documentation/doc-references/arkui-overview-0000001524432365
- 鸿蒙分布式能力: https://developer.harmonyos.com/cn/docs/documentation/doc-references/distributed-overview-0000001524460081
附录:项目文件结构
zuoyea/
├── AppScope/ # 应用全局配置
│ └── resources/ # 全局资源文件
├── entry/ # 主模块
│ ├── src/main/
│ │ ├── ets/
│ │ │ ├── data/ # 数据层
│ │ │ │ └── PoemData.ets
│ │ │ ├── model/ # 数据模型
│ │ │ │ └── Poem.ets
│ │ │ ├── pages/ # 页面组件
│ │ │ │ ├── Index.ets
│ │ │ │ ├── PoemListPage.ets
│ │ │ │ ├── PoemDetailPage.ets
│ │ │ │ ├── RecitePage.ets
│ │ │ │ └── ChallengePage.ets
│ │ │ ├── utils/ # 工具类
│ │ │ │ ├── AudioUtils.ets
│ │ │ │ └── StorageUtils.ets
│ │ │ └── entryability/ # 应用入口
│ │ └── resources/ # 模块资源文件
│ └── hvigorfile.ts # 构建配置
├── hvigor/ # 构建工具配置
└── build-profile.json5 # 项目构建配置
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)