系列文章:
《鸿蒙纪元》 是 张风捷特烈[1] 计划打造的一套 HarmonyOS 开发系列教程合集。致力于创作优质的鸿蒙原生学习资源,帮助开发者进入纯血鸿蒙的开发之中。本系列的所有代码将开源在 HarmonyUnit[2] 项目中:github: https://github.com/toly1994328/HarmonyUnit
gitee: https://gitee.com/toly1994328/HarmonyUnit
本文是《鸿蒙纪·梦始卷》 的第四章,上一篇我们基于计数器的小案例,继续优化界面表现。了解资源文件的使用、学会沉浸状态导航栏,实现全屏布局的方案。接下来,我们将通过几个有趣的小案例,沿着我的免费小册 《Flutter 入门教程》[3] 的脚步,踏上初入鸿蒙应用开发的取经之路。第一个小功能是 猜数字,本文将会学到:
[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-拆分文件维护[4]。如果 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) })
}
}
在布局上,猜数字在结构上和计数器非常类似,都是 上中下 结果,如下所示:• 顶部栏的标题需要缓成搜索框,右侧的按钮用于确认提交;• 中间区域展示信息,当生成随机数后,需要展示密文;• 底部的按钮在开始时可以点击生成随机数,猜数字过程中需要被禁用;当点击运行时,会检测当前输入和目标值的大小关系。并以红色和蓝色的区域提示用户。其中红蓝各占高度的一半;文字展示在色块中间。在布局上,红蓝色块可以叠放在底层,根据猜测的状态值决定展示效果:
功能和布局分析完了,接下来就进行实际的代码编写吧。本文只会完成基本的静态界面布局,具体的交互逻辑和界面状态的变化,将在下一篇中介绍。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-猜数字-静态界面[5]。
到这里,我们就完成了猜数字的静态界面布局。下一篇,将继续完善猜数字功能,实现交互与静态状态的变化。我们下次再见~[1] 张风捷特烈:
https://juejin.cn/user/149189281194766
[2] HarmonyUnit:
[3] 《Flutter 入门教程》:
https://juejin.cn/book/7212822723330834487/section
[4] 计数器-v6-拆分文件维护:
https://github.com/toly1994328/HarmonyUnit/tree/6062163bba15e7ac6bdfbf96f0dfd605f28139e2
[5] 计数器-v7-猜数字-静态界面:
https://github.com/toly1994328/HarmonyUnit/tree/6ed911dd396592fe88f50574c93aa84bf60e3d42
[6] 张风捷特烈:
https://juejin.cn/user/149189281194766
[7] 张风捷特烈:
https://space.bilibili.com/390457600
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!