专栏名称: 鸿洋
你好,欢迎关注鸿洋的公众号,每天为您推送高质量文章,让你每天都能涨知识。点击历史消息,查看所有已推送的文章,喜欢可以置顶本公众号。此外,本公众号支持投稿,如果你有原创的文章,希望通过本公众号发布,欢迎投稿。
目录
相关文章推荐
郭霖  ·  Android Resource资源管理 ·  2 天前  
鸿洋  ·  App前台,Activity会被回收吗? ·  2 天前  
郭霖  ·  一文了解 Gradle 插件 ·  5 天前  
stormzhang  ·  普通人靠利息躺平,可能吗? ·  5 天前  
郭霖  ·  使用 Jetpack Compose ... ·  1 周前  
51好读  ›  专栏  ›  鸿洋

鸿蒙系列教程#04 | 猜数字 - 需求与静态界面

鸿洋  · 公众号  · android  · 2024-12-03 08:35

正文

系列文章:

鸿蒙纪·系列教程#03 | 沉浸状态栏与资源使用
鸿蒙系列教程#02 | 从计数器认识布局基础
鸿蒙纪·系列教程#01 - 环境搭建与项目结构


《鸿蒙纪元》 是 张风捷特烈[1] 计划打造的一套 HarmonyOS 开发系列教程合集。致力于创作优质的鸿蒙原生学习资源,帮助开发者进入纯血鸿蒙的开发之中。本系列的所有代码将开源在 HarmonyUnit[2] 项目中:
github: https://github.com/toly1994328/HarmonyUnit
gitee: https://gitee.com/toly1994328/HarmonyUnit
本文是《鸿蒙纪·梦始卷》 的第四章,上一篇我们基于计数器的小案例,继续优化界面表现。了解资源文件的使用、学会沉浸状态导航栏,实现全屏布局的方案。

接下来,我们将通过几个有趣的小案例,沿着我的免费小册 《Flutter 入门教程》[3] 的脚步,踏上初入鸿蒙应用开发的取经之路。第一个小功能是 猜数字,本文将会学到:

[1]. 如何将代码拆分成多个文件维护。
[2]. 了解猜数字的交互功能,与分析需求。

[3]. 构建猜数字的静态界面。

1
多文件拆分

随着应用中功能的不断增加,如何组织代码结构是一个非常重要的课题。把所有代码都塞入一个文件中是不可取的。我们应该根据 功能需求 和 逻辑复用 情况,来综合考虑代码的组织。首先我们应该学会,一个项目中,多个文件之间该如何关联起来,共同服务于项目。
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 文件夹,然后界面中划分组件块交给每个文件维护。但拆分虽好,可不要贪杯哦 ~
2
猜数字界面交互与界面准备

猜数字是鸿蒙纪元的一个案例,功能比较简单,非常适合新手朋友入门学习。该需求中包含的知识点包括:
• 随机数的生成
• 输入框的使用
• 界面构建的练习
• 逻辑控制的练习
• 动画的简单使用

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. 静态界面布局
在布局上,猜数字在结构上和计数器非常类似,都是 上中下 结果,如下所示:
• 顶部栏的标题需要缓成搜索框,右侧的按钮用于确认提交;
• 中间区域展示信息,当生成随机数后,需要展示密文;
• 底部的按钮在开始时可以点击生成随机数,猜数字过程中需要被禁用;
开始时
生成随机数字
当点击运行时,会检测当前输入和目标值的大小关系。并以红色和蓝色的区域提示用户。其中红蓝各占高度的一半;文字展示在色块中间。在布局上,红蓝色块可以叠放在底层,根据猜测的状态值决定展示效果:
小了
大了


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-猜数字-静态界面[5]。

4
尾声


到这里,我们就完成了猜数字的静态界面布局。下一篇,将继续完善猜数字功能,实现交互与静态状态的变化。我们下次再见~
引用链接

[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^)┛明天见!