鸿蒙应用开发从入门到入行

第三天 - 登录案例、事件、双向绑定、装饰器

导读:在本篇文章里,您将掌握事件、装饰器、双向绑定等相关知识,并利用所学知识做一个待办列表的案例。

练手案例:登录界面

  • 开始之前,先说些题外话
  • 猫林老师发现不少同学可以独立写出来,我很欣慰。说明行动力、悟性、之前前端留下的布局思想都还在。希望各位同学和更多的朋友们都能参与进来。大家以后写完可以把自己的代码或者效果贴到评论区相互讨论。讨论的人越多越有学习氛围,在这里大家都可以找到志同道合的人。并且,每一次热烈的讨论都能激励猫林老师更认真迅速地去写下一篇文章。
  • 而且写教程文真的很需要花费额外时间,那就会挤占猫林老师想做爱做的事的时间😊。而且纯爱心发电其实动力并不足,所以也真的很需要各位读者提供热烈的正向回馈来激励猫林老师,所以大家请一定要多点赞、收藏、评论。要是可以,也分享给你周围想学鸿蒙的朋友。猫林老师保证把系列文章更新下去,让大家从文章里就能学到真东西,并且具备找工作能力。
  • 好了,话不多说,开始说回上次的作业案例,让我们先回顾一下作业的效果图:

    image-20240801220933118.png

  • 从上图分析可以发现整体上所有内容是从上往下布局,所以用Column作为根容器非常合适。
  • 然后里面可以分为8行元素,分别为:Image、Text、Text、TextInput、TextInput、Row、Button、Text,如图

    <img src="https://gitee.com/xpzll/newtypora/raw/master/noteimg/202408051149944.png" alt="image-20240804230722627" style="zoom:50%;" />

  • 这些都是比较容易看出来的布局,主要是给大家解释下 短信验证码登录忘记密码那一行,为什么还要用一个Row包起来呢?因为如果这两个文字不被Row包起来的话,那么父组件是Column,那 短信验证码登录忘记密码就会变成一行一个。所以用一个Row包起来,因为Row有从左到右布局子组件的能力,而这两个文字就需要从左到右,只不过一个在起点,一个在终点(即在首尾),所以这里到时候还可以给它做一个主轴上的布局为SpaceBetween
  • 其他的无非记得要让根容器Column铺满屏幕,也即宽高百分百,图片给宽度、登录界面给文字大小和加粗,登录帐号以使用更多服务改文字颜色、文字大小。两个TextInput给占位符,其中第二个TextInput记得要把type设置为password。其他剩余的三个label都是改文字颜色、字体大小。登录按钮给宽度铺满。然后整个页面是灰色,所以给Column设置背景色,再给TextInput设置背景色为白色。
  • 根据上述分析,代码如下

        Column() {
          Image($r('app.media.app_icon'))
            .width(100)
    
          Text('登录界面')
            .fontSize(24)
            .fontWeight(700)
          Text('登录账号以使用更多服务')
            .fontColor(Color.Gray)
            .fontSize(14)
    
          TextInput({ placeholder: '账号' })
            .backgroundColor('#fefefe')
    
    
          TextInput({ placeholder: '密码' })
            .backgroundColor('#fefefe')
            .type(InputType.Password)
    
          Row() {
            Text('短信验证码登录')
              .fontColor('#3172f3')
            Text('忘记密码')
              .fontColor('#3172f3')
          }
          .justifyContent(FlexAlign.SpaceBetween)
    
          Button('登录')
            .width('100%')
          Text('注册账号')
            .fontColor('#3172f3')
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
        .backgroundColor('#eff1f3')
  • 对应效果如下:

    <img src="https://gitee.com/xpzll/newtypora/raw/master/noteimg/202408051149945.png" alt="image-20240804232102798" style="zoom:50%;" />

  • 此时大家发现两个问题:

    1. 明明短信验证码那一行的Row给了 justifyContent(FlexAlign.SpaceBetween),但没生效,看着还是居中。为什么呢?
    2. 所有内容行与行之间没有间距,导致挨的太紧。
  • 这两个问题都很好很解决,分别如下

    1. 给了FlexAlign.SpaceBetween也没生效,是因为猫林老师上节课就说过大部分组件不给宽高就是靠内容撑开宽高,也即内容有大,Row就只有多大。所以你设置首尾对齐实际上它已经是首尾了,只不过因为Row就那么大,所以效果不动

      image-20240804232528991

      所以解决办法很简答:给Row一个width('100%')即可

    2. 行与行之间要设置间距可以给Column加space
  • 因此,改良代码如下(仅写出本次改动部分)

        Column({ space: 10 }) {
          ......
    
          Row() {
            Text('短信验证码登录')
              .fontColor('#3172f3')
            Text('忘记密码')
              .fontColor('#3172f3')
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
    
          ........
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
        .backgroundColor('#eff1f3')
  • 此时效果如下

    image-20240804232825289

  • 发现间距是有了,但是中间的账号、密码、短信验证码登录这歌区域跟上面和下面太近,需要把这部分跟上下加一些间距

    image-20240804233104386

  • 如上图所示,发现这个时候我们应该把中间这一部分作为一个整体,再统一设置整体的上下外间距即可。因此需要给中间的2个TextInput和它下面包住两个文字的Row再套一个父容器。这里请大家思考下,他们的父容器,用Column好还是Row好呢?

    • 没错,用Column好!因为用Row他们会从左往右排列,而我们依然要它从上到下,只不过多个父容器而已,所以用Column
  • 那分析完加父容器后,还有个问题。怎么设置这个父容器的距离外部的间距呢?会css的同学知道,是margin,没错,ArkTS里也是margin(如果不懂什么叫margin的请挑战到本文最后的附录:外间距与内间距,再回此继续观看),语法如下

    组件() {
      
    }
    .margin(间距数)
       
    // 例
    Column() {
       
    }
    .margin(20)  // 代表这个 Column 具体上下左右间距都是20
  • 如果你不需要上下左右间距都是同一个值,则可以传入一个对象,分别设置不同的margin,例

    Column() {
       
    }
    .margin({ left: 20, right: 10, top: 15, bottom: 30 } )  // 代表这个Column左间距20,右间距10,上间距15,下间距30
  • 也可以仅设置几个方向的间距,没设置方向的间距代表为0,例

    Column() {
       
    }
    .margin({ top: 10, bottom: 20 } )  // 代表这个Column上间距10,下间距20,左右间距因为没设置,那么则代表0也即没有间距
  • 因此,案例代码里先给中间部分加Column,且设置外间距。并且因为中间部分包了Colum后,他们各自之间也没间距了,因此给包住验证码登录文字的Row再加一个上间距,TextInput不用加,因为他们本身就要挨在一起,改动代码如下

        Column({ space: 10 }) {
          .........
    
          Column() {
            TextInput({ placeholder: '账号' })
              .backgroundColor('#fefefe')
    
    
            TextInput({ placeholder: '密码' })
              .backgroundColor('#fefefe')
              .type(InputType.Password)
    
            Row() {
              Text('短信验证码登录')
                .fontColor('#3172f3')
              Text('忘记密码')
                .fontColor('#3172f3')
            }
            .margin({ top: 10 }) 
          }
          .margin({ top: 20, bottom: 20 })
    
          .......
        }
  • 此时效果如下

    image-20240804234748972

  • 没错,现在跟最终效果图已经差不多了,但是发现左右两边都挨到边边了,而效果图需要左右两边都有点间距。这时候有两种解决办法:

    1. 给两个TextInput、Row、Button这四行设置左右外间距
    2. 给他们共同的父组件设置内间距
  • 很明显,用第二种给共同的父组件设置内间距办法更方便。但是ArkTS里如何设置呢?其实还是用padding,并且用法跟margin是一样的,例

    Column() {
      
    }
    .padding(20) // 上下左右四个方向内间距都是20
      
      
    Column() {
      
    }
    .padding({ top: 20, bottom: 10, left: 20, right: 25 }) // 上内间距20,下内间距10,左内间距20,右内间距25
      
    Column() {
      
    }
    .padding({ left: 20, right: 20 }) // 左内间距和右内间距都是20,上下没写则默认是0
  • 因此,再给根组件Column设置padding,且只需要左右间距即可

        Column({ space: 10 }) {
         ........
        }
        .......
        .padding({ left: 10, right: 10 })
  • 至此,一个完整的登录界面就写完了,你学废了吗?

    <img src="https://gitee.com/xpzll/newtypora/raw/master/noteimg/202408051149951.png" alt="image-20240804235452793" style="zoom:33%;" />

  • 本案例新知识:

    • margin: 外间距
    • padding :内间距

事件

  • 上面的登录案例中,我们目前点登录按钮是没有任何反应的。要想让它有反应,必须添加事件,在ArkTS中如何添加事件呢?
  • 语法

    组件() {
      
    }
    .on事件名( e => {
      // 事件处理代码
    } )
  • Button('登录')
    .onClick(e => {
      // 处理代码
    })
  • 注意:上面的Click里的C大写。后面的事件名如无特殊情况,都是要首字母大写,例如change事件,写的时候要加onChange,这跟前端里的全小写不一样,大家要注意。
  • e则是事件对象,但用的略少,不需要时,可以不写e,替换为小括号

    Button('登录')
    .onClick(() => {
      // 处理代码
    })

提示框

  • 如果现在,我希望点击按钮后弹出登录成功的提示框怎么办呢?
  • ArkUI里提供了PromptAction对象,专门用来做弹窗
  • 用法:

    • 先导入,再调用PromptAction对象的showToast方法,传入对象,配置提示信息,例
    import { promptAction } from '@kit.ArkUI'
      
      
    promptAction.showToast({
                  message: '提示消息', // 提示的文字
                  duration: 2000  // 显示时长,不填则默认为1500
    });
技巧:可以在写代码时,直接写promptAction,然后出提示后按回车,DevEco会自动帮你生成导入的代码,如下图

image-20240805092933739

  • 注意:

    • duration为提示框多久后消失(也即显示时长),可以不填,不填则默认为1500,并且最小值也是1500,最大值是10000。如果填写的数字小于1500,也按1500来显示,如果大于10000,也按10000来显示。
    • 单位是毫秒,1500即1.5秒
  • 例:

    promptAction.showToast({ message: '猫林老师教程真好!' })
    • 效果如下

    image-20240805101709023

声明组件内成员变量

  • 很多时候我们这个页面(组件)需要声明一些变量用来保存数据,和对其处理。那么怎么声明呢?
  • 一般会写在build函数的上面,struct关键子下面,即下图位置

    image-20240805093644027

  • 语法为:

    变量名: 类型 = 初始值
  • 例:

    userId: string = '' // 声明了一个名为 userId 的变量,它是字符串类型,初始值为空字符串
  • 变量声明好了,如何在代码中使用呢?一律前面加this访问,例

    this.userId

双向绑定

  • 学会声明成员变量后,我们在登录案例 里,声明两个变量,分别叫userIduserPwd,专门用来跟账号、密码输入框分别做双向绑定

    struct Index {
      // 成员变量列表
      userId: string = 'admin'
      userPwd: string = '123'
    
      build() {
        ........
      }
    }
插播双向绑定: 即数据一旦改变,界面跟着变。 界面输入内容有变化,数据也跟着变。
  • 那么ArkTS里如何让数据跟输入框做双向绑定呢?(Next版本后新增的语法)

    TextInput({ text: $$成员变量 })
    
    // 例
    TextInput( text: $$this.userId )
  • 接下来让我们把声明的userIduserPwd分别绑定到账号框和密码框

    TextInput({ placeholder: '账号', text: $$this.userId })
       .backgroundColor('#fefefe')
    
    
    TextInput({ placeholder: '密码', text: $$this.userPwd })
       .backgroundColor('#fefefe')
       .type(InputType.Password)
  • 此时保存代码会看到预览器里界面已经能显示绑定的数据了,如图

    image-20240805094941806

  • 那我们说双向绑定是:数据 -> 界面, 同样,界面的输入变化也会影响数据,那是否能呢?带着这个疑问,我们先给登录按钮加一个点击事件,点击事件里用console.log输出这两个变量的值。

    Button('登录')
       .width('100%')
       .onClick(() => {
          console.log(`账号:${this.userId}, 密码:${this.userPwd}`)
       })
注意:这里用到了模板字符串,一些同学可能不太理解这种字符串。这里说明一下:首先是用`这个符号包起来,跟单引号双引号都表示字符串,但区别在于模板字符串能很方便做字符串拼接,例如上面的代码,相当于是 '账号:' + this.userId + '密码:' + this.userPwd
  • 然后我们去预览起的界面上重新输入,再点按钮输出,看显示什么(具体看截图,可以看到在哪看console.log输出的内容)

    image-20240805095558522

  • 小结:

    • 在输入框里,使用成员变量前加 $$ 即可双向绑定
  • 需注意:

    • 目前$$仅能用在基本数据类型且绑定给内置组件

装饰器 - @State

  • 从上面的效果可以看到,已经实现了双向绑定,但此时存在一个问题:数据无法再触发界面更新
  • 例:修改登录的点击事件,在里面我修改userId的值,看界面是否能更新

          Button('登录')
            .width('100%')
            .onClick(() => {
              this.userId = '我要变'
              console.log('新值:' + this.userId)
            })
    • 结果如图

    image-20240805095939376

  • 原因:默认声明的成员变量不具备数据改变触发界面更新渲染的功能
  • 解决办法:需要使用装饰器
  • 装饰器:

    • 修饰某些数据、函数,使其具有特殊作用
    • 装饰器有很多种,本次学的叫 @State,注意首字母大写
    • @State作用:

      • 当被@State修饰的变量数据改变时,UI会发生对应的重新渲染。
    • 用法

      @State 变量: 类型 = '初始值'
  • 让我们测试一下,来到登录案例里找到userId,给它加@State试试

    @State userId: string = 'admin'
  • 效果如下图:

    image-20240805100821296

  • 但是同样的,加了装饰器后会有轻微的性能开销,即使这种开销甚至可以忽略不计。但是对于对性能优化有要求的App而言,则建议。如果数据仅仅只是用来内部参与运算或临时接收界面输入,不需要将来重新更新UI,就不加@State
  • 装饰器不光只有@State,后续还有很多,学一个记一个。
  • 小结:

    • 装饰器

      • 修饰数据、函数等,使其具有特殊作用
    • @State

      • 被@State修饰的变量能当它数据改变时,UI会发生对应的重新渲染

实现登录功能

  • 最后,我们给登录案例收个尾,当用户点击登录按钮时,如果输入的账号是admin,密码是123,则提示登录成功,否则登录失败(将来学发送请求,如今暂时写死硬判断)
  • 代码如下

         Button('登录')
            .width('100%')
            .onClick(() => {
              if (this.userId === 'admin' && this.userPwd === '123') {
                promptAction.showToast({ message: '登录成功' })
              } else {
                promptAction.showToast({ message: '账号或密码错误' })
              }
            })
  • 效果如图:

    <img src="https://gitee.com/xpzll/newtypora/raw/master/noteimg/202408051149961.png" alt="image-20240805101607722" style="zoom:50%;" />

总结内容

  • 本文中我们学了事件、提示框、成员变量声明、双向绑定、装饰器。我们回顾一下
  • 事件:

    • on事件名,事件名首字母大写,例如:onClick、onChange
  • 提示框:

    • 需要先导入

      import { promptAction } from '@kit.ArkUI'
    • 然后使用

      promptAction.showToast( { message: '提示信息', duration:时长  } )
    • 技巧:可以直接输入promptAction,出提示后,按回车,DevEco会自动导入
  • 声明成员变量

    变量: 类型 = 初始值
  • 默认情况下,变量改变不会触发界面重新渲染,因此需要装饰器:@State
  • 双向绑定

    $$this.变量名

课后练习

  • 判断题:请回答对或者错

    1. promptAction.showToast方法,传入duration属性,值为1000,代表提示框在1秒后消失
    2. 成员变量与输入框双向绑定时,成员变量前面不用加this
    3. 数据如果不加@State,就不能进行计算

练习答案

  1. 错 2. 错 3. 错

    ​ (错错错,是我的错。热恋的时候怎么不说,生活的无奈我已好困惑,你能不能不要再啰嗦)--- 请唱出来

附加练习

  • 如上图所示,做一个年度待办目标的列表。
  • 本案例功能比较丰富,各位能做多少做多少。本案例也会贯穿后面好几天的教学,所以涉及非常多新知识,做不出来也正常。
  • 提示:打勾部份可以用Image也可以用Checkbox,如需要做出布局,需要自行根据文档预习ProgressStackList

互动环节

  • 你觉得鸿蒙开发跟你以前会的开发,区别大吗?欢迎留下你的观点。
  • 最后,创作不易,请不要吝啬您的点赞、关注、收藏、转发!

猫林老师
4 声望2 粉丝

十年IT教育