项目源码地址
项目源码已发布到GitCode平台, 方便开发者进行下载和使用。
效果演示
前言
在移动应用开发中,底部菜单栏是用户交互的重要组成部分,尤其对于阅读类应用来说,一个功能完善、交互友好的底部菜单栏能够极大提升用户体验。本教程将详细讲解如何在HarmonyOS应用中实现一个功能丰富的阅读应用底部菜单栏,包括翻页方式选择、界面设置等功能。
技术要点
本教程涉及以下HarmonyOS开发技术点:
- ArkUI组件化开发
- 状态管理与数据同步
- 动画效果实现
- 事件处理与交互
- 条件渲染与可见性控制
底部菜单栏结构设计
我们的底部菜单栏主要包含以下几个部分:
- 底部图标导航栏(阅读列表、自由阅读、发现、亮度调节、设置)
- 翻页方式选择面板(左右翻页、上下翻页、覆盖翻页)
- 阅读设置面板(文本朗读、字体大小、背景颜色、背景图片)
- 评论区入口
代码实现
1. 组件结构与状态定义
首先,我们需要定义组件的基本结构和状态变量:
@Component
export struct BottomView {
// @Link装饰器:父子双向同步。@Link装饰的变量与其父组件中的数据源共享相同的值。
@Link filledName: string; // 当前选中的底部图标名称
@Link buttonClickedName: string; // 点击按钮的名称,用来判断是否已被点击
@Link isVisible: boolean; // 用来判断翻页方式视图是否显示
@Link isCommentVisible: boolean; // 用来判断评论视图是否显示
@Link isMenuViewVisible: boolean; // 用来判断上下菜单视图是否显示
@State buttonNameList: Array<string> = [STRINGCONFIGURATION.LEFTRIGHTFLIPPAGENAME,
STRINGCONFIGURATION.UPDOWNFLIPPAGENAME,
STRINGCONFIGURATION.COVERFLIPPAGENAME]
@Link bgColor: string; // 背景颜色
@Link textSize: number; // 文本大小
@State isTextReader: boolean = false; // 文本朗读状态
@State isTextSize: boolean = false; // 字体大小状态
@State isbgColor: boolean = false; // 背景颜色状态
@Link isbgImage: boolean; // 背景图片状态
@Link currentPageNum: number; // 当前页码
// 播放文章列表
@Prop readInfoList: TextReader.ReadInfo[] = [];
@Prop selectedReadInfo: TextReader.ReadInfo = this.readInfoList[0];
// 用于显示当前页的按钮状态
@State isInit: boolean = false; // 初始化状态
}
这里使用了HarmonyOS的几种状态装饰器:
@Link
:实现父子组件间的双向数据同步@State
:管理组件内部状态@Prop
:接收父组件传递的只读数据
2. 底部图标栏实现
底部图标栏是用户最常交互的区域,包含了多个功能入口:
Row() {
Image(this.filledName === STRINGCONFIGURATION.PAGEFLIPVIEWLIST ?
$r('app.media.flippage_view_list_filled') : $r('app.media.flippage_view_list'))
.width($r('app.string.pageflip_bottomview_row_image_width'))
.height($r('app.string.pageflip_bottomview_row_image_height'))
.objectFit(ImageFit.Contain)
.padding($r('app.integer.flippage_padding_small'))
.onClick(() => {
this.clickAnimate(STRINGCONFIGURATION.PAGEFLIPVIEWLIST);
})
// 其他图标(自由阅读、发现、亮度、设置)的实现类似
// ...
Image(this.filledName === STRINGCONFIGURATION.PAGEFLIPSETTINGS ?
$r('app.media.flippage_settings_filled') : $r('app.media.flippage_settings'))
.width($r('app.string.pageflip_bottomview_row_image_width'))
.height($r('app.string.pageflip_bottomview_row_image_height'))
.objectFit(ImageFit.Contain)
.padding($r('app.integer.flippage_padding_small'))
.onClick(() => {
animateTo({
duration: CONFIGURATION.PAGEFLIPTOASTDURATION,
curve: Curve.Linear,
}, () => {
if (this.filledName === STRINGCONFIGURATION.PAGEFLIPSETTINGS) {
this.filledName = '';
this.isVisible = false;
this.isCommentVisible = true;
} else {
this.filledName = STRINGCONFIGURATION.PAGEFLIPSETTINGS;
this.isVisible = true;
this.isCommentVisible = false;
}
});
})
.id('setting')
}
这里的关键点:
- 使用条件渲染切换图标的填充/未填充状态
- 为每个图标添加点击事件处理
- 使用动画效果增强交互体验
3. 翻页方式选择面板
翻页方式选择面板允许用户选择不同的翻页模式:
Flex({ justifyContent: FlexAlign.SpaceBetween }) {
ForEach(this.buttonNameList, (item: string) => {
Button(item, { type: ButtonType.Capsule })
.backgroundColor($r('app.color.pageflip_button_backgroundcolor'))
.fontColor(this.buttonClickedName === item ?
$r('app.color.pageflip_button_click_fontcolor') :
$r('app.color.pageflip_button_fontcolor'))
.margin({ left: $r('app.integer.flippage_margin_small'),
right: $r('app.integer.flippage_margin_small') })
.borderWidth(CONFIGURATION.PAGEFLIPBORDERWIDTH)
.onClick(() => {
if (this.buttonClickedName !== item) {
this.buttonClickedName = item;
this.isMenuViewVisible = false;
this.filledName = '';
this.isVisible = false;
}
})
}, (item: string) => item)
}
.margin({ top: $r('app.integer.flippage_margin_small'),
bottom: $r('app.integer.flippage_margin_small') })
.visibility(this.isVisible ? Visibility.Visible : Visibility.None)
这里的关键点:
- 使用
ForEach
循环渲染按钮列表 - 根据选中状态动态改变按钮文字颜色
- 通过
visibility
属性控制面板的显示和隐藏
4. 阅读设置面板
阅读设置面板提供了文本朗读、字体大小、背景颜色等功能:
Flex({ justifyContent: FlexAlign.SpaceBetween }) {
Button($r('app.string.pageflip_button_text_reader'), { type: ButtonType.Capsule })
.backgroundColor($r('app.color.pageflip_button_backgroundcolor'))
.fontColor(this.isTextReader ?
$r('app.color.pageflip_button_click_fontcolor') :
$r('app.color.pageflip_button_fontcolor'))
.margin({ left: $r('app.integer.flippage_margin_small'),
right: $r('app.integer.flippage_margin_small') })
.borderWidth(CONFIGURATION.PAGEFLIPBORDERWIDTH)
.onClick( () => {
this.setEventListener();
if(!this.isTextReader) {
// 朗读控件起播,拉起播放器面板并开始播放
TextReader.showPanel();
TextReader.start(this.readInfoList, this.selectedReadInfo?.id).then(() => {
logger.info('TextReader succeeded in starting');
}).catch((e: BusinessError) => {
logger.error(`TextReader failed to start. Code: ${e.code}, message: ${e.message}`);
})
}
else {
// 朗读控件停止朗读,执行播放面板的关闭
TextReader.stop().then(() => {
logger.info(`item TextReader succeeded in stopping.`);
}).catch((e: BusinessError) => {
logger.error(`TextReader failed to stop. Code: ${e.code}, message: ${e.message}`);
})
TextReader.hidePanel();
}
this.isTextReader = !this.isTextReader;
emitter.emit({ eventId: 0, priority: 0 }, {
data: {
isTextReader: this.isTextReader
}
})
this.isMenuViewVisible = false;
this.filledName = '';
this.isVisible = false;
})
.id('textReading')
// 字体大小和背景颜色按钮实现类似
// ...
}
.margin({ top: $r('app.integer.flippage_margin_small'),
bottom: $r('app.integer.flippage_margin_small') })
.visibility(this.isVisible ? Visibility.Visible : Visibility.None)
这里的关键点:
- 文本朗读功能的实现,包括启动和停止朗读
- 使用
emitter
发送事件通知其他组件状态变化 - 使用Promise处理异步操作
5. 动画效果实现
为了增强用户体验,我们为菜单切换添加了动画效果:
clickAnimate(name: string) {
animateTo({
duration: CONFIGURATION.PAGEFLIPTOASTDURATION,
curve: Curve.Linear,
}, () => {
if (this.filledName === name) {
this.filledName = '';
this.isCommentVisible = true;
} else {
promptAction.showToast({
message: $r('app.string.pageflip_default_toast'),
duration: CONFIGURATION.PAGEFLIPTOASTDURATION
});
this.filledName = name;
this.isVisible = false;
this.isCommentVisible = false;
}
})
}
这里的关键点:
- 使用
animateTo
实现平滑的状态过渡 - 根据当前状态决定动画后的界面展示
- 使用
promptAction.showToast
提供用户反馈
文本朗读功能实现
文本朗读是阅读应用的重要功能,我们使用HarmonyOS的TextReader
组件实现:
1. 初始化朗读控件
async init() {
// 设置朗读参数
const readerParam: TextReader.ReaderParam = {
isVoiceBrandVisible: true,
businessBrandInfo: {
panelName: STRINGCONFIGURATION.XIAOYIREADING
}
}
try{
// 初始化朗读控件
await TextReader.init(getContext(this), readerParam);
this.isInit = true;
} catch (err) {
logger.error(`TextReader failed to init. Code: ${err.code}, message: ${err.message}`);
}
}
2. 设置事件监听
setEventListener(){
TextReader.on('stop', () => {
this.isTextReader = false;
});
TextReader.on('stateChange', (state: TextReader.ReadState) => {
// 当前正在播放的文章播放完成
if(state.state === CONFIGURATION.COMPLETED) {
// 当朗读的为最后一页
if(Number(state.id) === this.readInfoList.length) {
this.currentPageNum = Number(state.id);
} else {
this.currentPageNum = Number(state.id) + 1;
}
}
});
TextReader.on('eventPanel', (pe: TextReader.PanelEvent) => {
// 点击上一条按钮
if(pe.click === 'BPC_03') {
this.currentPageNum = Number(pe.id);
// 点击下一条按钮
} else if(pe.click === 'BPC_04') {
this.currentPageNum = Number(pe.id);
}
});
}
最佳实践与注意事项
- 状态管理:合理使用
@Link
、@State
和@Prop
装饰器管理组件状态 - 条件渲染:使用三元表达式和
visibility
属性控制UI元素的显示和隐藏 - 事件处理:为UI元素添加点击事件处理函数,实现交互逻辑
- 动画效果:使用
animateTo
实现平滑的状态过渡 - 资源引用:使用
$r
引用应用资源,便于国际化和主题切换 - 错误处理:使用try-catch和Promise的catch方法处理可能的错误
总结
通过本教程,我们学习了如何在HarmonyOS应用中实现一个功能完善的阅读应用底部菜单栏。这个菜单栏不仅提供了基本的导航功能,还集成了翻页方式选择、文本朗读、字体大小调整、背景颜色设置等高级功能,大大提升了用户的阅读体验。