从零开始开发纯血鸿蒙应用之实现起始页
从零开始开发纯血鸿蒙应用
- 一、前言
- 二、主要页面
- 三、应用起始页
- 四、MainPageContent 实现
- 1、一级结构
- 2、二级结构
- 2.1、EmptyContent
- 2.2、FileListContent
- 2.2.1、ViewAction:
- 2.2.2、EditAction
- 2.2.3、DeleteAction
- 2.2.4、ShareAction
- 五、载入起始页的时机
- 五、总结
一、前言
从本篇开始,将对 TxtEdit 的业务功能进行逐一实现,而业务功能是宿主在一个个具体的应用页面上的,所以,当务之急,便是将 TxtEdit 的主要页面、即用于编辑和浏览纯文本文件的页面进行实现。
目前,第一版实现中,主要页面有如下几个:
像那些用于说明app开发者信息,以及进行问题反馈等页面,个人认为属于非关键的主要页面,因此,在应用开发过程中,其优先级较低,可以放在最后去实现。
二、主要页面
TxtEdit 在版本 1.0.x 的主要页面,共有三套:
- 应用起始页,由一个 page 文件组成
- 内部文件处理页,由两个 page 文件组成,分别用于编辑和浏览应用的沙箱文件
- 外部文件处理页,也是两个 page 文件组成,用于浏览和导入。
三、应用起始页
应用起始页,由 Index.ets
文件实现,页面路由为 pages/Index
,它的代码内容结构如下:
根据 build 函数体,可知 TxtEdit 的起始页,和众多app的起始页一样,都是采用 Tabs 组件进行页面结构搭建,我这里,由于追求简洁和实用,Tabs 里面的 TabContent 也即所谓 Tab 页,只有两个。
在鸿蒙UI中,Tabs 组件具有横向和纵向两种方向,并且 tabBar 也即 Tab 页标题栏,允许传入一个注解 @Builder
的 UI 构建函数,从而实现更美观或更个性化的应用起始页,由于本人美术天赋较差,设计不出让人眼前一亮的 UI 样式,只有如下的简简单单的:
@Builder
tabBuilder(title: string, targetIndex: number) {
Column() {
Text(title)
.fontColor(this.currentIndex === targetIndex ? '#ff0af10a' : '#fff5f6f5')
}
.width('100%')
.height(60)
.justifyContent(FlexAlign.Center)
}
只做了根据Tab页是否被选中进行Tab标题颜色的变更。
为了达到 Tabs 组件居底布局,页面标题居顶布局,我将 Tabs 套在一个使用了 BottomColumn
样式的 Column 文件中,再将这个 Column 放在一个使用 RootTopColumn
样式的 Column 文件中。
TxtEdit 的起始页中的第一个 Tab 页,即文件 Tab 页,顾名思义,用于展示文件列表
,方便用户进行内部文件的操作和管理,这时候,涉及到无内部文件和有内部文件
两种状态的页面内容,要以什么样的方式进行载入?
方案不外乎两种,一种是在 TabContent 里面直接做条件渲染,根据条件使用不同的Content ,另一种是TabContent 里面使用一个直接展示的 Content,将条件渲染下移到该Content里面。
本人采取的就是第二种方案:
build() {
Column(){
PageTitleBar({ title: 'TxtEdit'})
Column(){
Tabs(){
TabContent(){
Column(){
MainPageContent()
}.attributeModifier(this.centerColumn)
}.tabBar(this.tabBuilder('文件', 0))
TabContent(){
AboutPageContent()
}.tabBar(this.tabBuilder('关于', 1))
}.attributeModifier(this.blackTabs)
.onChange((index) => {
this.currentIndex = index
})
}.attributeModifier(this.bottomColumn)
}.attributeModifier(this.rootContainer)
}
四、MainPageContent 实现
在我尚未叙述具体实现时,屏幕前的你,对于 MainPageContent
的实现,心里有什么样的方案?是否认为,用一个类似 if-else 的语法结构,就能够实现无文件页面和有文件页面的载入显示?
如果是这样,那么你大概没有考虑到,用户场景存在着一种情况,就是从有文件到无文件
的状态回退。此外,实时的 UI 刷新,比如用户创建第一个文件结束后,回到起始页,应当将用户创建的文件展示在文件列表中,而不是要用户杀掉app进程重新进入才能看到。
总的来说,app页面的实现,并非是想当然而,需要将可能的动态刷新场景兜底住,才能给用户一个较好的UI体验和交互操作体验。
1、一级结构
本篇后续,以及后面的每一篇博文,为了控制篇幅,主要是内容字符数,源码会贴的比较少,更多以图片的形式展示,完整源码还请访问gitee仓库获取,当然了,gitCode仓库也是准备了的。
由简入繁,先看一下函数体都收起时的内容。整体上,在 MainPageContent 中,最开始的地方,定义了一组辅助 UI 刷新的私有字段或状态变量,其中,状态变量是触发 UI 刷新所不可或缺的。
在页面即将渲染时,也即生命周期函数 aboutToAppear
所对应的阶段,需要做一些初始化工作:
利用项目内部 API,即 FileUtil 去获取内部文件的文件名列表,并且根据列表的空与非空,决定最终渲染的内容是 EmptyContent
还是 FileListContent
,我这里所采用的条件渲染控制,并非鸿蒙UI所支持的 if-else 语法,而是组件属性 visibility
,如此做法,其考量就是让UI代码更纯粹些:
为了满足实时刷新UI内容的需要,doRefresh
函数做了如下工作:
虽然,和 aboutToAppear 很像,却也是必不可少的,毕竟已经通过装饰器 @Watch
与 refresh 做了绑定,而 refresh 实际上是一个应用全局的UI状态存储
中的字段,利用应用全局的UI状态存储机制,可以做到在B页面控制A页面进行UI更新,具体用法后面会给出。
2、二级结构
MainPageContent 的二级结构,实际对应的就是 EmptyContent 和 FileListContent。鉴于 EmptyContent 的实现更为简单,所以分析顺序就从 EmptyContent 开始。
2.1、EmptyContent
如图所示,开始处也是一批字段的定义,其中的 dialog 字段实际是一个 自定义弹窗
:
通过代入 CustomDialogController
的参数 alignment,将弹窗设置为页面居中显示,这里需要提醒一下 DevEco Studio 的 UI 预览器和真机显示上,在自定义弹窗上的区别,预览器显示弹窗是默认居中的,但真机则不是如此;另一个带入 CustomDialogController
的参数,用于控制是否允许自动关闭弹窗,即点击弹窗外的区域时是否允许关闭弹窗
,显然,我这里设置为不允许,因此,弹窗的关闭只能通过显示在弹窗上的两个按钮去进行:
这个弹窗的打开动作,我将它放在“新建或导入按钮”中。
2.2、FileListContent
FileListContent 的结构,与 EmptyContent 的结构很相似,只不过“新建或导入”按钮上方,变成了一个列表组件。列表组件的列表项,根据fileList
也即内部文件名列表动态生成,每个列表项都是由 RecentFileItem
实现:
RecentFileItem 的实现如下:
其显示效果如图:
点击“操作”文本,会有弹出式菜单
显示出来,该菜单提供四个操作:查看、编辑、分享和删除。弹出式菜单的使用,只需在鸿蒙UI组件的bindMenu
传入菜单绘制方法即可,也就是这里的 OptionMenu
方法:
四个操作的响应逻辑,通过 RecentFileItem 的四个函数参数载入,每一个具体实现逻辑,下面一一介绍:
2.2.1、ViewAction:
该动作的含义,就是浏览当前列表项对应的沙箱文件,实现代码比较简单,就是将对应的沙箱文件名通过路由参数带入到 ViewFilePage
中。
2.2.2、EditAction
动作含义就是编辑当前列表项对应的沙箱文件,由于实现代码与 ViewAction 类似,便不过多赘述。
2.2.3、DeleteAction
动作含义就是删除当前列表项对应的沙箱文件,在实现方面,主要用项目内部 API FileUtil.deleteFile(this.ctx, item)
实现文件的删除,而在删除成功的回调处理中,会对 TxtEdit 的起始页进行 UI 更新,即当最后一个内部文件被删除时,起始页重新显示无文件对应的页面内容;否则仍旧显示文件列表。
2.2.4、ShareAction
该动作的含义,就是分享当前列表项对应的沙箱文件,在具体实现方面,也是用了一个项目内部 API,其都对应的具体代码如下:
主要就是利用鸿蒙系统提供的系统分享面板进行文件的分享:
要呼出如上的系统分享面板,需要做以下几方面的工作:
1)判断要分享的文件是否存在,避免引起其他应用出现异常
2)获取待分享文件对应的 utdTypeId
3)借助 systemShare.SharedData
包装分享内容,因为系统分享面板只接受 systemShare.SharedData
的数据
4)创建并持有 systemShare.ShareController
,即系统分享面板控制器
5)借助系统分享面板控制器拉起系统分享面板,并对是否成功拉起面板进行回调处理
五、载入起始页的时机
在鸿蒙应用开发框架中,应用起始页的载入时机为,应用声明周期函数 onWindowStageCreate
:
在该函数中,会通过系统 API windowStage.loadContent
去加载某个 Page 作为起始页,而这里就是 'pages/Index'
。
五、总结
正所谓台上一分钟台下十年功,看起来很简单的应用起始页,实现起来往往需要对多种动态刷新和操作交互进行处理,更需要保证页面间的数据流透传方向不出错、以及应用全局范围的信号起作用等。