专栏名称: 鸿洋
你好,欢迎关注鸿洋的公众号,每天为您推送高质量文章,让你每天都能涨知识。点击历史消息,查看所有已推送的文章,喜欢可以置顶本公众号。此外,本公众号支持投稿,如果你有原创的文章,希望通过本公众号发布,欢迎投稿。
目录
相关文章推荐
鸿洋  ·  Android anr排查之sp卡顿 ·  2 天前  
郭霖  ·  Android 16首个开发者预览版到来 ·  3 天前  
鸿洋  ·  再学安卓 - init进程 ·  3 天前  
51好读  ›  专栏  ›  鸿洋

鸿蒙系列教程#05 | 猜数字 - 交互与状态变化

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

正文

系列教程:

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


《鸿蒙纪元》 是 张风捷特烈[1] 计划打造的一套 HarmonyOS 开发系列教程合集。致力于创作优质的鸿蒙原生学习资源,帮助开发者进入纯血鸿蒙的开发之中。本系列的所有代码将开源在 HarmonyUnit[2] 项目中:
github: https://github.com/toly1994328/HarmonyUnit
gitee:
https://gitee.com/toly1994328/HarmonyUnit
鸿蒙纪元 系列文章列表可在《文章总集》[3] 或 【github 项目首页】[4] 查看。
本文是《鸿蒙纪·梦始卷》 的第五章,上一篇我们介绍了 猜数字 的基本功能,并完成了基本的界面布局。了解通过拆分文件,将代码逻辑拆解成多个文件模块维护:

本文将继续完善猜数字需求,完成交互与状态变化。

1
状态数据分析


在编写代码之前,最好仔细 分析需求 。归纳一下界面中交互时会变化的状态量,包括它的类型、修改的时机、以及状态量变化的逻辑。


启动
点击开始
输入猜测
猜数字需求中,可以分析出以下需要变化的状态:

这样就可以通过 @State 定义组件中的状态量,如下所示:
@Component
export struct GuessingPage {
  @State guessing: boolean = false;
  @State secret: number = 0;
  @State input: string = '';
  @State result: CheckResult = CheckResult.none;


2
开启猜数字事件

点击右下角的按钮开始生成数字,可以定义一个 start 函数来维护这个事件中状态量的变化逻辑。如下所示:

• 将 guessing 置为 true ,表示游戏开始;
• 将 secret 设置为 (0,100) 间的随机整数;
• 将 result 置为 none 、input 清空,表示新的一局开始。
start(): void {
  this.guessing = true;
  this.secret = Math.floor(Math.random() * 100);
  this.result = CheckResult.none;
  this.input =''
}
在 Button 的 onClick 事件中,触发 this.start() 即可。

在编码习惯上,建议事件由独立的函数处理,而 不是 直接写在 onClick 里面(如下反例)。单独封装函数处理,可以让构建逻辑相对精简,也可以独立出修改状态数据的逻辑。可谓一举两得,特别是对了较为复杂的逻辑。


3
输入框与数据双向绑定

输入框视图 在输入过程中可以影响 input 状态值;反过来,设置 input 状态值也可以改变 输入框视图 。这就是:
状态数据和视图组件的 双向绑定关系
鸿蒙开发中某些组件与状态的双向绑定,可以通过 $$this. 进行实现,如下所示:
@Builder
titleInput() {
  TextInput({ 
  placeholder: '输入 0~99 数字'
  text: $$this.input,
 })
 /// 略同...
}
坑点: 封装时插槽组件构建的 this 指向
在开发过程中遇到一个非常坑的点,在 GuessingPage 中定义的 titleInput 插槽,在运行时无法访问到类中的状态成员。结果调试发现,直接将 titleInput 作为入参传给 AppBar ,运行该方法的 this 居然是 AppBar。怪不得无法访问 GuessingPage 中的成员呢。

---->[之前传参方式, titleInput 中this 是 AppBar]----
AppBar(
  {
    /// 略...
    titleSlot: this.titleInput,
  }
)

我们可以通过闭包调用方法的方式构建组件,这样就能有期望的 this 指向:

---->[修改传参方式,通过闭包,以调用的方式,此时 titleInput 中的 this 是 GuessingPage]----
AppBar(
  {
    /// 略...
    titleSlot: () => {this.titleInput()},
  }
)

4
核心校验逻辑


点击顶部栏右侧的运行按钮时,会触发比较逻辑。检验输入值和目标值的大小关系;上一章介绍说过,校验的结果通过 CheckResult 枚举表示:


enum CheckResult {
  none,
  bigger,
  smaller,
  equal,
}
校验的逻辑封装为 checkResult 方法,其中会处理状态数据的变化,如下所示:
仅当输入非空、游戏开始后才需要进行校验,如果输入不是数字则不处理。然后计算输入值和目标值的差值,更新 this.result 即可:
checkResult(): void {
  if (this.input === '' || !this.guessing) {
    return;
  }
  const guess: number = Number(this.input);
  if (Number.isNaN(guess)) {
    return;
  }
  const diff = guess - this.secret;
  if (diff == 0) {
    this.result = CheckResult.equal
    this.guessing = false;
    this.input =''
  }
  if (diff > 0) {
    this.result = CheckResult.bigger
  }
  if (diff 0) {
    this.result = CheckResult.smaller
  }
}

5
声名式 UI : 数据决定界面
在声名式的 UI 框架中,都是基于数据来决定界面的构建。状态数据界面表现的决定因素,比如中间的描述信息,在不同状态数据下有不同的界面表现:

声名式 UI 的另一大特点是:
界面构建的逻辑可以被 分离 ,局部界面只需要依赖它所数据。
比如中间的介绍信息,需要依赖 result、guessing、result 三个状态数据;我们可以将其封装为 InfomationDisplay 组件,来单独维护中间区域的界面构建逻辑。在主界面构建时,只需要使用该组件,传入数据即可:

这样 InfomationDisplay 中就可以专注于处理,中间内容根据状态数据展示不同的文字。如下所示, info 和 value 两个函数用于处理展示的字符串。这就是职责的分离,每件事都有专门负责的人,出了问题或需要更新需求时,就可以迅速找到负责这件事的类、函数。

在子组件中,可以通过 @Prop 声明 父子单向同步 的参数,这样父层级传入的数据变化时,可以自动通知更新当前组件:

@Component
struct InfomationDisplay {
  @Prop result: CheckResult = CheckResult.none;
  @Prop guessing: boolean = false;
  @Prop secret: number = 0;

  info(): string {
    if (this.result == CheckResult.equal) {
      return '恭喜你猜对啦~';
    }
    if (!this.guessing) {
      return '点击生成随机数';
    }
    return '开始输入猜数字吧~';
  }

  value(): string {
    if (this.guessing) {
      return '**';
    }
    return this.secret.toString();
  }

  build() {
    Column() {
      Text(this.info())
      Text(this.value()).fontSize(46).fontColor('#727272')
    }.width('100%').height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

很多初学者可能没有拆分的意识,喜欢把所有的逻辑一股脑全塞在一块,最后形成一个难以维护的臃肿项目。我建议,大家在敲代码之前,一定要好好分析一下功能需求和界面结构;认清 交互事件 和 状态数据 流向。争取有一个好的代码结构,可以让项目代码非常整洁、易读、清晰。

这里提交一个小里程碑:v8-猜数字-交互完成[5]。

6
文件拆分维护
GuessingPage.ets 中的代码目前有 200 多行,看起来代码还是比较清晰的。最后,我们运用一些上一章文件拆分的思想,对它进行拆解,分多个文件共同维护,进一步提高代码的可读性:
如下所示,将 GuessingPage.ets的代码按照类型和功能进行整理,放入 page/guessing 文件夹下;实现其中主要包括 状态数据的维护 和 界面构建逻辑 ,分别将它们放入 model 和 view 文件夹下。这样一眼就能看到,那个文件在负责哪件3事:


此时 状态数据 和 数据变化逻辑 集中在 GuessingState 中,我们后续将称修改数据的逻辑为 业务逻辑。此时就实现了最简单的 业务逻辑 和 视图构建逻辑 的分离:
---->[pages/guessing/model/GuessingState.ets]----
import { CheckResult } from "./CheckResult";

export class GuessingState{
  guessing: boolean = false;
  secret: number = 0;
  input: string = '';
  result: CheckResult = CheckResult.none;

  checkResult(): void {
    if (this.input === '' || !this.guessing) {
      return;
    }
    const guess: number = Number(this.input);
    if (Number.isNaN(guess)) {
      return;
    }
    const diff = guess - this.secret;

    if (diff == 0) {
      this.result = CheckResult.equal
      this.guessing = false;
      this.input =''
    }
    if (diff > 0) {
      this.result = CheckResult.bigger
    }
    if (diff 0) {
      this.result = CheckResult.smaller
    }
  }

  start(): void {
    this.guessing = true;
    this.secret = Math.floor(Math.random() * 100);
    this.result = CheckResult.none;
    this.input =''
  }
}

在视图中,只需要依赖业务逻辑对象即可:

视图的交互行为,触发事件影响数据的逻辑,调用业务逻辑对象中的方法处理即可,这样可以大大减轻 GuessingPage.ets 中的代码压力,从而专注于界面构建逻辑。

对于更加复杂的业务逻辑,还可以继续根据职责进行拆分。不过目前的猜数字项目这样就已经非常不错了,各个文件各司其职,共同维护猜数字小系统的运行。
这里提交一个小里程碑:v9-猜数字-代码结构优化[6]。大家可以和 V8 对比, 感受一下代码结构带来的效力。
7
尾声


到这里,我们就完成了猜数字的基本功能。下一篇,我们将了解一下动画的使用,在每次猜测时,结果面板都可以动画表现。

    引用链接

    [1] 张风捷特烈: https://juejin.cn/user/149189281194766
    [2] HarmonyUnit:https://github.com/toly1994328/HarmonyUnit

    [3] 《文章总集》:https://juejin.cn/column/7392249184122060836

    [4] 【github 项目首页】: https://github.com/toly1994328/HarmonyUnit
    [5] v8-猜数字-交互完成: https://github.com/toly1994328/HarmonyUnit/tree/42aca099038678360434ba892d370b60b6baf98c
    [6] v9-猜数字-代码结构优化: https://github.com/toly1994328/HarmonyUnit/tree/dc33ec1b19c11208f9bbcf8fcf3a3b372765de6a
    [7] 张风捷特烈: https://juejin.cn/user/149189281194766
    [8] 张风捷特烈: https://space.bilibili.com/390457600



    最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!



    扫一扫 关注我的公众号

    如果你想要跟大家分享你的文章,欢迎投稿~


    ┏(^0^)┛明天见!

    推荐文章
    鸿洋  ·  Android anr排查之sp卡顿
    2 天前
    鸿洋  ·  再学安卓 - init进程
    3 天前
    金融行业网  ·  银行理财业务最全解析
    7 年前
    视觉志  ·  人在江湖飘,身材很重要
    7 年前
    杂学杂问  ·  杂学杂问 5月16日早读分享
    7 年前