在当今数字化时代,如何让传统文化与现代科技相结合,成为了一个值得思考的问题。诗词作为中国传统文化的重要组成部分,承载着丰富的历史信息和文化内涵。为了让更多人了解和欣赏诗词的魅力,我们决定开发一款基于HarmonyOS NEXT的诗词元服务应用。这款应用不仅能够提供诗词的欣赏功能,还能通过现代化的交互方式,让传统诗词焕发出新的生命力。
项目目标
目标是打造一个精神的栖息地,为文人墨客和诗词爱好者提供一个互动交流的平台。同时,我们希望通过这个项目,让更多人能快速上述鸿蒙应用开发,不要说你不会,更不要说难。直接从实战上手,直接看到学习成果,获得成就感。完整展示从开发到打包,从签名上架的全套流程。同时能够欣赏到中国传统文化的精髓,体验诗词带来的美好感受。在开发过程中,我们将注重用户体验,确保用户能够便捷地翻阅诗词,以及获取诗词的详细信息。

功能特性
- 诗词展示:展示诗词的标题、作者、内容,支持翻页操作和查看诗词注释。
- 对话框交互:包含诗词解说对话框和建议与反馈对话框,提升用户交互体验。
- 页面导航:可通过 AtomicServiceTabs 组件在首页和关于页面间切换。
- 个人中心:展示作者及个人信息。
诗词展示
诗词展示功能是应用的核心之一,将展示诗词的标题、作者和正文内容。用户可以通过翻页操作来浏览不同的诗词。此外,我们还为每首诗词提供了详细的注释,包括词句的解释、背景介绍和作者的注解,帮助用户更好地理解诗词。
对话框交互
我们加入了诗词解说对话框和建议与反馈对话框,以提升用户的交互体验。诗词解说对话框可以显示诗词的详细解释、背景和注解,而建议与反馈对话框则让用户可以向我们提出建议或反馈意见,帮助我们不断改进应用。
页面导航
我们使用了AtomicServiceTabs
组件,通过底部导航栏实现了首页和关于页面的切换。首页主要展示诗词内容,而关于页面则介绍项目背景和开发团队的信息。
数据内容
诗词数据以本地JSON格式存储,示例如下:
json
{
"poet": "伟人",
"title": "致彭德怀同志",
"content": "山高路远坑深,大军纵横驰奔。\r\n谁敢横刀立马?唯我彭大将军。",
"intro": "1935年10月21日,红军到达吴起镇,毛泽东亲自督战迎敌,部署好战斗后,把指挥事宜交给彭德怀处理,战斗结束后,他当场赋此诗赠予彭德怀。这首诗中字里行间跳动着凯歌的欢快音符,更跳动着革命者那颗"爱将"之心。",
"comment": "",
"trans": "吴起镇山险路长沟深,中央红军骑马奔驰杀敌。有谁敢横刀立马,只有我彭德怀大将军。",
"annot": "坑深:地势险峻。\r\n唯我彭大将军:毛泽东写诗赠于,首句即用电文句,但改"沟深"为"坑深"。彭德怀收到这首诗后,将诗的末句"唯我彭大将军"改为"唯我英勇红军",然后将原诗还给毛泽东。"
}
实现概述
读取JSON文件
为了实现诗词数据的展示,我们首先需要读取本地的JSON文件。以下是读取JSON文件的代码实现:
typescript
import { common } from "@kit.AbilityKit";
import { util } from "@kit.ArkTS";
/**
* @author:
* @date: 2024/12/7 13:59
* @description: 读取JSON文件
* @version:
*/
export function ReadJsonFile(fileStr: string): Promise<Array<Record<string, Object>>>{
return new Promise<Array<Record<string, Object>>>(async (success, error) => {
try {
let context: common.UIAbilityContext = getContext() as common.UIAbilityContext;
let jsonFile = await context.resourceManager.getRawFileContent(fileStr);
let jsonString: string = util.TextDecoder.create('utf-8', {ignoreBOM: true}).decodeToString(jsonFile, { stream: false});
success(JSON.parse(jsonString))
} catch (e) {
error([])
}
})
}
首页实现
首页通过AtomicServiceTabs
组件实现页签切换,并展示诗词内容。以下是首页的实现代码:
typescript
import { AboutView } from '../view/AboutView';
import { HomeView } from '../view/HomeView';
import { AtomicServiceTabs, TabBarOptions, TabBarPosition } from '@ohos.atomicservice.AtomicServiceTabs';
import { CustomConstants } from '../common/constants/CustomConstants';
import { AtomicServiceNavigation } from '@ohos.atomicservice.AtomicServiceNavigation';
import { ViewData } from '../common/entity/ViewData';
import PersistentStorage from '@ohos.data.PersistentStorage';
PersistentStorage.persistProp<ViewData[]>(CustomConstants.PRELOAD_DATA_KEY, []);
PersistentStorage.persistProp<string>(CustomConstants.PRELOAD_STATUS_KEY, "succeed");
@Entry
@Component
struct Index {
@State currentIndex: number = 0;
@State title: ResourceStr = $r('app.string.app_name');
childNavStack: NavPathStack = new NavPathStack();
@Builder
tabHomeContent() {
HomeView()
}
@Builder
tabAboutContent() {
AboutView()
}
@Builder
navigationContent() {
AtomicServiceTabs({
tabContents: [
() => {
this.tabHomeContent()
},
() => {
this.tabAboutContent()
}
],
tabBarOptionsArray: [
new TabBarOptions($r('app.media.house'), '', $r('app.color.grayscale_color'), $r('app.color.title_font_color')),
new TabBarOptions($r('app.media.person'), '', $r('app.color.grayscale_color'), $r('app.color.title_font_color'))
],
tabBarPosition: TabBarPosition.BOTTOM,
barBackgroundColor: $r('app.color.primary_color'),
barOverlap: true,
onTabBarClick: (index: number) => {
if (index === 0) {
this.title = $r('app.string.app_name');
} else {
this.title = "关于我们";
}
}
})
}
@Builder
pageMap(name: string) {}
build() {
Column() {
AtomicServiceNavigation({
navigationContent: () => {
this.navigationContent();
},
title: this.title,
titleOptions: {
backgroundColor: $r('app.color.primary_color'),
isBlurEnabled: true
},
navDestinationBuilder: this.pageMap,
navPathStack: this.childNavStack,
mode: NavigationMode.Stack
})
}
.width(CustomConstants.PAGE_FULL)
.height(CustomConstants.PAGE_FULL)
}
}
诗词展示实现
在HomeView
中,我们实现了诗词的展示和翻页功能。以下是诗词展示的实现代码:
typescript
import { curves } from "@kit.ArkUI";
import { CustomConstants } from "../common/constants/CustomConstants";
import { IntroDialogView } from "../common/dialog/IntroDialogView";
import { ReadJsonFile } from "../common/utils/ReadJsonFile";
import AlertDialog from '@ohos.promptDialog.AlertDialog';
import { DialogAlignment, BlurStyle } from '@ohos.promptDialog';
@Preview
@Component
export struct HomeView {
@State viewData: Array<Record<string, any>> = [];
@State currentIndex: number = 0;
re: RegExp = new RegExp("[。!?]");
async aboutToAppear(): Promise<void> {
await ReadJsonFile("data.json5").then((data) => {
this.viewData = data as Array<Record<string, any>>;
});
}
build() {
Column() {
Stack() {
Scroll() {
Flex({ direction: FlexDirection.Row, wrap: FlexWrap.NoWrap }) {
Column({ space: CustomConstants.ROW_COLUMN_SPACES[3] }) {
Text(this.viewData[this.currentIndex]?.title ?? "")
.width($r('app.float.size_20'))
.fontSize($r('app.float.font_size_18'))
.fontWeight(FontWeight.Bolder)
.fontColor($r('app.color.title_font_color'))
Text(this.viewData[this.currentIndex]?.poet ?? "")
.width($r('app.float.size_20'))
.fontSize($r('app.float.font_size_14'))
.fontWeight(FontWeight.Normal)
.textAlign(TextAlign.Center)
.fontColor($r('app.color.start_window_background'))
.backgroundColor($r('app.color.tag_color'))
.backgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THIN)
.borderRadius($r('app.float.size_16'))
.padding({
top: $r('app.float.size_16'),
bottom: $r('app.float.size_16')
})
}
.justifyContent(FlexAlign.Center)
.padding({
left: $r('app.float.size_8'),
right: $r('app.float.size_8')
})
Scroll() {
Scroll() {
Row({ space: 10 }) {
ForEach(this.viewData[this.currentIndex]?.content.split(this.re), (item: string) => {
if (item.trim() != "") {
Column({ space: CustomConstants.ROW_COLUMN_SPACES[3] }) {
Text(item.trim().replace("\n", "").replace("\r", "") + "。")
.width($r('app.float.size_20'))
.fontSize($r('app.float.font_size_18'))
.fontColor($r('app.color.title_font_color'))
.fontWeight(FontWeight.Medium)
}
.justifyContent(FlexAlign.Start)
.padding({
left: $r('app.float.size_8'),
right: $r('app.float.size_8')
})
}
})
}
.justifyContent(FlexAlign.Start)
.alignItems(VerticalAlign.Top)
.padding({
left: $r('app.float.size_8'),
right: $r('app.float.size_8')
})
.transition(this.effect)
}
.layoutWeight(1)
.scrollBar(BarState.Off)
.scrollable(ScrollDirection.Horizontal)
.edgeEffect(EdgeEffect.Spring)
}
.layoutWeight(1)
.scrollBar(BarState.Off)
.scrollable(ScrollDirection.Vertical)
.edgeEffect(EdgeEffect.Spring)
}
.padding($r('app.float.size_20'))
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width(CustomConstants.PAGE_FULL)
.height(CustomConstants.PAGE_FULL)
.borderRadius($r('app.float.size_16'))
.shadow({
radius: $r('app.float.size_10'),
color: $r('app.color.primary_color')
})
Row() {
Button() {
// 上一页
Text() {
SymbolSpan($r('sys.symbol.chevron_left'))
.fontSize($r('app.float.font_size_24'))
.fontColor([$r('app.color.start_window_background')])
}
.width($r('app.float.size_81'))
.height($r('app.float.size_32'))
.textAlign(TextAlign.Center)
}
.type(ButtonType.Capsule)
.backgroundColor($r('app.color.secondary_color'))
.onClick(() => {
this.currentIndex--;
if (this.currentIndex < 0) {
this.currentIndex = 0;
}
})
// 注释
Button() {
Text() {
SymbolSpan($r('sys.symbol.keyboard_badge_zhuyin'))
.fontSize($r('app.float.font_size_24'))
.fontColor([$r('app.color.start_window_background')])
}
.width($r('app.float.size_48'))
.height($r('app.float.size_48'))
.textAlign(TextAlign.Center)
}
.type(ButtonType.Circle)
.backgroundColor($r('app.color.primary_color'))
.onClick(() => {
AlertDialog.show({
title: this.viewData[this.currentIndex].title + '【释义】',
message: '解释:\n' + this.viewData[this.currentIndex].trans + '\n\n赏析:\n' + this.viewData[this.currentIndex].intro + '\n\n注解:\n' + this.viewData[this.currentIndex].annot + '\n\n评价:\n' + this.viewData[this.currentIndex].comment,
autoCancel: true,
alignment: DialogAlignment.Center,
backgroundColor: $r('app.color.primary_color'),
backgroundBlurStyle: BlurStyle.NONE
})
})
// 下一页
Button() {
Text() {
SymbolSpan($r('sys.symbol.chevron_right'))
.fontSize($r('app.float.font_size_24'))
.fontColor([$r('app.color.start_window_background')])
}
.width($r('app.float.size_81'))
.height($r('app.float.size_32'))
.textAlign(TextAlign.Center)
}
.type(ButtonType.Capsule)
.backgroundColor($r('app.color.secondary_color'))
.onClick(() => {
this.currentIndex++;
if (this.currentIndex >= this.viewData.length) {
this.currentIndex = 0;
}
})
}
.width(CustomConstants.PAGE_FULL)
.height($r('app.float.size_48'))
.justifyContent(FlexAlign.SpaceEvenly)
}
.width(CustomConstants.PAGE_FULL)
.height(CustomConstants.PAGE_FULL)
.alignContent(Alignment.Bottom)
}
.width(CustomConstants.PAGE_FULL)
.height(CustomConstants.PAGE_FULL)
.backgroundColor($r('app.color.mask_color'))
.padding({
top: $r('app.float.size_12'),
right: $r('app.float.size_16'),
bottom: $r('app.float.size_64'),
left: $r('app.float.size_16')
})
}
// 创建TransitionEffect
private effect: TransitionEffect =
// 创建默认透明度转场效果,并指指定springMotion(0.6, 0.8)曲线
TransitionEffect.OPACITY.animation({ curve: curves.springMotion(0.6, 0.8) })
// 通过combine方法,这里的动画参数会跟随上面的TransitionEffect,也就是springMotion(0.6, 0.8)
.combine(TransitionEffect.scale({ x: 0, y: 0 }))
// 添加旋转转场效果,这里的动画参数会跟随上面带animation的TransitionEffect,也就是springMotion()
.combine(TransitionEffect.rotate({ angle: 90 }))
// 添加平移转场效果,这里的动画参数使用指定的springMotion()
.combine(TransitionEffect.translate({ y: 150 }).animation({ curve: curves.springMotion() }))
// 添加move转场效果,这里的动画参数会跟随上面的TransitionEffect,也就是springMotion()
.combine(TransitionEffect.move(TransitionEdge.END));
}
总结
通过上述代码,我们实现了从读取JSON文件到展示诗词内容的全流程。接下来的开发中,我们将进一步优化用户体验,增强应用功能,确保用户能够流畅地欣赏到中国传统文化的瑰宝。
作者介绍
作者:csdn猫哥
原文链接:https://blog.csdn.net/yyz_1987
团队介绍
坚果派团队由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉等相关内容,团队成员聚集在北京、上海、南京、深圳、广州、宁夏等地,目前已开发鸿蒙原生应用和三方库60+,欢迎交流。