《鸿蒙纪元》 是 张风捷特烈 计划打造的一套 HarmonyOS 开发系列教程合集。致力于创作优质的鸿蒙原生学习资源,帮助开发者进入纯血鸿蒙的开发之中。本系列的所有代码将开源在 HarmonyUnit 项目中:
github
: github.com/toly1994328…
gitee
: gitee.com/toly1994328…
本文是《鸿蒙纪·梦始卷》 的第四章,上一篇我们基于计数器的小案例,继续优化界面表现。了解资源文件的使用、学会沉浸状态导航栏,实现全屏布局的方案。
接下来,我们将通过几个有趣的小案例,沿着我的免费小册 《Flutter 入门教程》 的脚步,踏上初入鸿蒙应用开发的取经之路。第一个小功能是 猜数字
,本文将会学到:
[1]
. 如何将代码拆分成多个文件维护。[2]
. 了解猜数字的交互功能,与分析需求。[3]
. 构建猜数字的静态界面。
一、多文件拆分
随着应用中功能的不断增加,如何组织代码结构是一个非常重要的课题。把所有代码都塞入一个文件中是不可取的。我们应该根据 功能需求
和 逻辑复用
情况,来综合考虑代码的组织。首先我们应该学会,一个项目中,多个文件之间该如何关联起来,共同服务于项目。
1. 拆分计数器代码
之前计数器的所有代码都放在 Index.ets
中,会让入口代码显得冗长。根据功能需求,计数器是一个独立的功能;另外其中的 AppBar 组件其他界面可以复用。所以可以进行如下调节:
- 创建
components
文件夹,盛放打算复用的封装组件,将 AppBar 组件单独分文件维护; - 在
pages
文件夹下,创建CounterPage
维护计数器的功能需求。
2. 文件的导入与导出
此时 Index
组件中,就可以引入 CounterPage
实现构建逻辑。另外底部导航的高度避让,属于全应用的事,可以在 Index
中处理。也减轻了 CounterPage
的负担,这就是 明确功能需求的职责 ,谁该做什么事,就能够更专注地维护。这些在代码编写前就应该梳理清楚。
导入其他文件中的组件,可以使用 import
关键字,被导入的组件需要通过 export
导出:
import { CounterPage } from "./CounterPage";
@Entry
@Component
struct Index {
@StorageProp('bottomRectHeight')
bottomRectHeight: number = 0;
build() {
Column() {
CounterPage()
}.padding({ bottom: px2vp(this.bottomRectHeight) })
}
}
文件拆分完后,这里提交一个小里程碑:计数器-v6-拆分文件维护。如果 CounterPage 功能需求界面比较复杂,也可以对 CounterPage 的代码进一步拆分。比如创建一个 counter
文件夹,然后界面中划分组件块交给每个文件维护。但拆分虽好,可不要贪杯哦 ~
二、猜数字界面交互与界面准备
猜数字是鸿蒙纪元的一个案例,功能比较简单,非常适合新手朋友入门学习。该需求中包含的知识点包括:
- 随机数的生成
- 输入框的使用
- 界面构建的练习
- 逻辑控制的练习
- 动画的简单使用
1. 界面交互介绍
下面是两个最基础的交互:
- 点击按钮生成 0~99 的随机数,并将随机数密文隐藏。
- 头部的输入框,点击时弹出软键盘,可输入猜测的数字。
点击生成随机数 | 可输入文字 |
---|---|
如下所示,点击右上角的运行按钮,可以比较输入猜测值和生成值的大小,并在界面上通过两个色块进行提示。每次比较时,提示面板中的文字会有动画的变化,给出交互示意。
比较结果:小了 | 比较结果:大了 |
---|---|
这三个交互就是本案例的所有功能需求。你可以找几个朋友一起玩这个猜数字的小游戏,比如随机生成一个数后,每人输入一个数,最后猜中的人获取胜利。其中控制猜测的范围,使其更利于自己猜出结果,也是一点斗智斗勇。
2. 增加猜数字界面
现在可以增加一个 GuessingPage.ets
的代码负责维护猜数字的功能:
---->[GuessingPage.ets]----
@Component
export struct GuessingPage {
build() {
//TODO 构建界面
}
}
目前暂时还没有接触导航,可以在入口中将 CounterPage
注释一下,展示 GuessingPage
:
---->[Index.ets]----
import { GuessingPage } from './GuessingPage';
@Entry
@Component
struct Index {
@StorageProp('bottomRectHeight')
bottomRectHeight: number = 0;
build() {
Column() {
// CounterPage()
GuessingPage()
}.padding({ bottom: px2vp(this.bottomRectHeight) })
}
}
3. 静态界面布局
在布局上,猜数字在结构上和计数器非常类似,都是 上中下 结果,如下所示:
- 顶部栏的标题需要缓成搜索框,右侧的按钮用于确认提交;
- 中间区域展示信息,当生成随机数后,需要展示密文;
- 底部的按钮在开始时可以点击生成随机数,猜数字过程中需要被禁用;
开始时 | 生成随机数字 |
---|---|
当点击运行时,会检测当前输入和目标值的大小关系。并以红色和蓝色的区域提示用户。其中红蓝各占高度的一半;文字展示在色块中间。在布局上,红蓝色块可以叠放在底层,根据猜测的状态值决定展示效果:
小了 | 大了 |
---|---|
三、静态界面构建
功能和布局分析完了,接下来就进行实际的代码编写吧。本文只会完成基本的静态界面布局,具体的交互逻辑和界面状态的变化,将在下一篇中介绍。
1. 头部栏 AppBar 优化
在当前需求中,头部栏的标题是自定义的组件。而我们之前封装的 AppBar 标题只能是文字。其实代码并不需要在一开始就尽善尽美,随着功能需求的增加,可以逐步迭代封装的组件,使之更具有通用价值。现在可以将 AppBar 中间内容的也通过插槽的方式,交由外界提供:
如下所示,增加 titleSlot
构造器,其中 titleBuilder
可以指定默认的展示组件;这样也不会影响之前的 title 属性:
@Component
export struct AppBar {
/// 略同...
@Builder
titleBuilder() {
Text(this.title)
.fontSize($r('app.float.app_bar_title_size')).fontWeight(FontWeight.Bold)
.fontColor($r('sys.color.white'))
}
@BuilderParam titleSlot: () => void = this.titleBuilder;
此时在 AppBar 中的 titleSlot
参数中传入 titleInput 构造器,使用 TextInput 展示输入框。layoutWeight
方法可以设置占位比重,让输入框的宽度延展剩余区域:
@Builder
titleInput() {
TextInput({ placeholder: '输入 0~99 数字' })
.layoutWeight(1)
.margin({ left: 8, right: 8 })
.backgroundColor('#F3F6F9')
}
2. 状态量与按钮、文字构建
按钮的状态想要在猜测状态数变为不可点击。所以需要一个状态量来记录输入,如下的 guessing
布尔值,在构建过程中,通过 Button#enabled
设置按钮是否可用;使用 guessing 状态设置按钮的背景色:
按钮可用 | 按钮不可用 |
---|---|
@State guessing: boolean = true;
@Builder
button() {
Button({ type: ButtonType.Circle, stateEffect: true }) {
SymbolGlyph($r('sys.symbol.scope'))
.fontSize(24)
.fontColor([Color.White])
.fontWeight(FontWeight.Bold)
}
.width(56)
.height(56)
.margin({ right: 20, bottom: 16 })
.backgroundColor(this.guessing ? '#9e9e9c' : $r('app.color.theme_color'))
.enabled(!this.guessing)
.onClick(() => this.gen())
}
同理,中间区域的内容也可以通过 guessing
状态来控制构建的逻辑。在构建中可以通过 if(flag) A else B 来根据 flag 决定展示 A 视图还是 B 视图;这里提取 buildCounterDisplay 方法来构建中间区域内容;如果构建逻辑比较复杂,也可以拆分成组件单独维护。
开始 | 生成随机数后 |
---|---|
@Builder
buildCounterDisplay(){
if(!this.guessing)
Column() {
Text('点击生成随机数')
Text('0').fontSize(46).fontColor('#727272')
}.width('100%').height('100%')
.justifyContent(FlexAlign.Center)
else
Column() {
Text('开始输入猜数字吧~')
Text('**').fontSize(46).fontColor('#727272')
}.width('100%').height('100%')
.justifyContent(FlexAlign.Center)
}
3. 结果展示区域构建
仔细观察需求的界面,大了、小了的布局特性是一致的。只不过颜色和文字不同,我们可以拆分出一个 ResultDisplay
组件来展示它。而不是类似的代码在 build 中复制粘贴两遍。结果展示所依赖的数据,这里提炼出 CheckResult 枚举,更便于使用:
@Component
struct ResultDisplay {
private result: CheckResult = CheckResult.none;
build() {
Column() {
Text(resultLabel(this.result))
.fontSize(24)
.fontColor(Color.White)
.fontWeight(FontWeight.Bold)
}
.layoutWeight(1)
.width('100%')
.justifyContent(FlexAlign.Center)
.backgroundColor(resultColor(this.result))
}
}
CheckResult 表示比较的结果,一开始为 none ,比较后会有大了、小了、相等三个结果。另外,通过了 resultLabel
和 resultColor
提供枚举对应的文字和颜色:
enum CheckResult {
none,
bigger,
smaller,
equal,
}
function resultLabel(result: CheckResult): string | Resource {
switch (result) {
case CheckResult.bigger:
return '大了';
case CheckResult.smaller:
return '小了';
}
return '';
}
function resultColor(result: CheckResult): ResourceColor {
switch (result) {
case CheckResult.bigger:
return '#ff5454';
case CheckResult.smaller:
return '#448afc';
}
return '';
}
最后,将两个 ResultDisplay 叠放在主界面之下,即可实现期望的静态布局效果:
Stack() {
Column() {
ResultDisplay({ result: CheckResult.bigger })
ResultDisplay({ result: CheckResult.none })
}
this.buildCounterDisplay()
this.button()
}.layoutWeight(1)
这里提交一个小里程碑:计数器-v7-猜数字-静态界面。
尾声
到这里,我们就完成了猜数字的静态界面布局。下一篇,将继续完善猜数字功能,实现交互与静态状态的变化。我们下次再见~
更多文章和视频知识资讯,大家可以关注我的公众号、掘金和 B 站 。