鸿蒙应用开发从入门到入行 - 篇6:数据监听器、滚动、侧滑功能

简介: 在本篇文章里,您将掌握监听器、滚动、侧滑等相关内容,助力你开发出更具交互的案例。

本次整体学习目标介绍

  • 我们本次继续完成这个年度计划案例,并依然通过需求驱动的方式学习新知识点

  • 回顾:上一篇文章我们已经完成了TodoMain的显示,并且完成了从TodoItem里修改完成状态后,也能同步到TodoMain,目前案例还差TodoHeaderTodoInput部分未完成

对上篇文最后留下的互动问题做解答

  • 上篇问到:在本案例中TodoItem里的数据打勾变化后(完成状态变化),TodoMain已经能成功收到改动了。那么它的父组件,最早持有数组的Index有收到改动吗?并说出理由
  • 相信到这篇文章的时候,大家已经有正确答案了,这里猫林也做个解答:
    • Index里的数组也跟着变了。因为TodoMain接收Index传递过来的数组时用的是@Link装饰器。而@Link具有父子同步的效果,因此子里TodoMain对数组改动,也能同步到父的Index

年度目标案例 - 统计总量

  • 此时头部的TodoHeader里,需要显示总目标数以及已完成数。也就意味着依赖了存放目标列表的数组

  • 步骤如下

    • 来到TodoHeader里声明一个成员变量接收数组,因为这个组件里仅仅只需要展示,无需改动数组。所以用@Prop修饰即可,并再声明个变量用来记录已完成数量,并把数组长度与已完成数量渲染到进度条与Text里,代码如下

      ```dart
      import { TodoModel } from '../viewmodel/TodoModel'

      @Component
      export struct TodoHeader {
      // 父传递数组进来,数组长度即为目标总数
      @Prop todoList: TodoModel[] = []
      // 声明一个变量记录已完成数量,将来用
      @State finishedCount: number = 0

  build() {
    Row() {
      .........
          // 以下是界面改动部分
      Stack() {
        Progress({ value: this.finishedCount, total: this.todoList.length, type: ProgressType.Ring })
          .width(80)
          .height(80)
        Text(`${this.finishedCount} / ${this.todoList.length}`)
      }
    }
    ...........
  }
}
```
  • 来到Index做传递

    .........
    
    
    struct Index {
         
      ..........
    
      build() {
         
        Column({
          space: 20 }) {
         
          // 主要是这里传参
          TodoHeader({
          todoList: this.totalFlags })
          ........
        }
      }
    }
    
  • 经过以上两步此时已能显示出总目标量,并且不管将来是添加了新目标还是删除了旧目标,这里的总量都会跟着变

    • 因为@Prop修饰后,父的数组有改变会自动同步到子,所以子里也会有最新数组数据

年度目标案例 - 统计已完成数 - 监听器的使用

  • 到目前为止,统计已完成数,也即我们在TodoHeader里声明的成员变量finishedCount依然保持着初始化值:0

  • 此时我们需要想办法统计出已完成数,并且随着数组的改变,已完成数也要重新统计

    • 例如:数组里打勾或者取消打勾、删除打勾项等都要实时统计
  • 所以,我们需要的是一种监听数据是否有变化,一旦变化就能执行我们预设的逻辑的机制

  • 鸿蒙开发里提供了这种机制,就叫数据监听器

  • 语法

    其他装饰器 ('方法名') 变量名: 变量类型
    
    // 例
     ('getFinished') todoList: TodoModel[] = []
    
  • 释意:

    • 代表监听todoList是否有更改,一旦有更改就调用getFinished这个方法进行处理
  • 注意:

    • 使用监听器时,请记得必须要声明它绑定的方法,像上面代码,我们需要声明成员方法,如下
    export struct TodoHeader {
         
    
       ('getFinished') todoList: TodoModel[] = []
    
      getFinished() {
         
        console.log('数组改变了')
      }
    
      ........
    }
    
  • 此时来到界面我们随便找几个目标打勾,会发现在控制台成功输出数组改变了

    image-20240813232542503

  • 此时,已成功监听到数组变动,所以只需在这个方法里,统计出数组里一共有多少个已完成的数量,然后赋值给finishedCount即可

  • 代码如下(注意:这里为了照顾所有编程语言使用者,我用最简单的for循环统计。如懂JS可以使用reduce)

      getFinished() {
         
        let total = 0
        for (let i = 0; i < this.todoList.length; i++) {
         
          if (this.todoList[i].finished) {
         
            total++
          }
        }
    
        this.finishedCount = total
      }
    
  • 恭喜,至此头部的统计部分我们全部完成

    image-20240814000227536

年度目标案例 - 添加新目标

  • 此时完成TodoInput里的添加新目标功能:
  • 整体思路为
    • 把数组传递给TodoInput,然后给输入框加输入完成事件,在事件里把输入的内容加到数组里即可(用@Link装饰,子里变了也能同步到父)
  • 步骤:

    • 来到TodoInput,声明一个状态变量接收父的数组

      import {
              TodoModel } from '../viewmodel/TodoModel'
      .........
      export struct TodoInput {
             
      
         todoList: TodoModel[]
        ........
      }
      
    • 来到Index做传递

        build() {
             
          Column({
              space: 20 }) {
             
            ......
            TodoInput({
              todoList: this.totalFlags })
            ......
          }
            .....
        }
      
    • 这个时候,只需要输入框加输入完成事件,事件里做非空判断,不为空把输入内容加入到数组里,再清空输入框即可

      • 输入完成即按下输入法的回车键。事件为:onSubmit
      ......
      
      import {
              promptAction } from '@kit.ArkUI'
      
      
      export struct TodoInput {
             
        .........
      
        build() {
             
      
          Row() {
             
            TextInput({
              placeholder: '请输入新目标', text: $$this.newFlag })
              .width('90%')
              .onSubmit(() => {
             
                if (this.newFlag) {
             
                  this.todoList.push({
             
                    text: this.newFlag,
                    finished: false // 新增加的新年目标,默认情况就该是未完成状态
                  })
      
                  this.newFlag = '' // 情况输入框
      
                } else {
             
                  promptAction.showToast({
              message: '输入内容不能为空' })
                }
              })
          }
          ........
        }
      }
      
    • 提示:这里想做的更严谨的同学还可以做去重处理

年度目标案例 - 目标列表滚动

  • 此时本案例存在一个问题:当数据过多,一页无法展示时,居然没有出现滚动条

    • 大家可以通过输入一些新目标来测试,你会发现铺满页面后发现无法滚动
    • 如下图

    image-20240814175419080

  • 原因:

    • 在鸿蒙应用开发中,不是所有组件都具备内容滚动功能

    • 本例中,包住每一项目标的是Column(如下代码),而Column不具备滚动功能

      Column({
              space: 10 }) {
             
            ForEach(this.todoList, (item: TodoModel, index: number) => {
             
              // 这里用了ES6简写,完整写法是 TodoItem({ item: item })代表把ForEach里的item传递给TodoItem需要的item
              TodoItem({
             
                item, onChange: () => {
             
                  this.changeStatus(item, index)
                }
              })
      })
      
    • 所以可以给Column外面包一个Scroll组件,如

      Scroll() {
             
        Column({
              space: 10 }) {
             
            .......
          })
         .width('100%')
      }
      .height(300)
      
    • 这样即具备了滚动功能。

    • 这里为什么还给Scroll设置了高度呢

      • 如果不设置高度,将无法滚动
      • 原因:
        • 当内容超出容器大小时,我们才需要滚动以及才能拥有滚动。所以,如果内容并没有超出容器,是不具备滚动功能的
        • 而如果你不给Scroll设置高度,它的高度就是根据内容自动计算得来,内容一共有多高,它就有多少高度。这样就导致内容永远没超出Scroll,就不具备滚动功能
    • 思考:高度写死300合理吗?

      • 肯定不合理,就拿本案例来说,TodoMain是这个页面最后一个组件,它如果高度设小了,就导致后面留了很多空,比较丑,如下图

        image-20240814233806416

      • 但是数字写高了又不好,有可能在其他小屏幕会超出,在大屏幕也不够

      • 所以我们希望Scroll这个容器的高度能占用剩余高度

      • 这时候可以用LayoutWeight属性,设置这个容器在父容器里主轴方向剩余空间的占比

      • 故代码改动如下

        Scroll() {
                 
          Column({
                  space: 10 }) {
                 
              .......
            })
           .width('100%')
        }
        .layoutWeight(1)
        
      • 解释;

        • 这代表让Scroll在父容器剩余空间里占比1。因为只有设置了它占比,所以就相当于它自己独占剩余部分(如果只有一个容器设置占比,写1或者写99效果都一样)
    • 这里再稍微说明一下Scroll组件其他特点

      • 能让自己的子组件具备滚动功能,且它里面只能放一个子组件。
      • 它自己一般不直接存放内容,而是让子组件存放内容。
  • 上面虽然用Scroll能起到滚动效果,但我们这里不用它。因为,我们还需要具备侧滑功能,Scroll并不方便

    • 事实上Scroll开发中也相对用的少
  • 如果既要能滚动,又要具备侧滑效果,应该用List组件

知识点 - List组件

  • List组件称之为列表组件,专门用来展示一堆相同宽度的列表项(例如TodoItem)。适合连续、多行呈现同类数据(例如我们本案例里的数组)

  • 特点:当列表项达到一定数量,内容超过屏幕大小时,可以自动提供滚动功能

  • 使用语法

    List() {
         
      ListItem() {
         
        内容
      }
    }
    
  • 说明:List里仅能放ListItem(每一项)或ListGroup(分组),组件关系如下图

    img

  • 例如:

    image-20240814230459658

  • 虽然List里组件只能放ListItemListItemGroup,但它内部可以使用if-elseForEach语法做条件渲染、循环渲染

  • ListRowColumn这些容器一样,也可以设置space参数来控制每一个列表项之间的间距
  • 并且通过List里的ListItem能设置侧滑
  • 具体的关于List还有一些特点,将在下面把它用在本案例里再具体讲解,这里仅仅解释基本作用

年度目标案例 - 使用List改写TodoMain

注意:在开始之前,如果你按照上面的学习在案例TodoMain组件里使用过Scroll来学习它,那记得把Scroll删掉

  • 很简单,其实就是把原来的Column改成List,再把ForEach里的TodoItemListItem包起来即可,代码如下

    .......
    
    
    export struct TodoMain {
         
    
      ........
    
      build() {
         
    
        // 把之前的根容器从Column换成了List,List也能用Space属性
        List({
          space: 10 }) {
         
          ForEach(this.todoList, (item: TodoModel, index: number) => {
         
            // 把TodoItem用ListItem包起来,因为List组件里只能放`ListItem`
            ListItem() {
         
    
              TodoItem({
         
                item, onChange: () => {
         
                  this.changeStatus(item, index)
                }
              })
            }
          })
        }
        .width('100%')
        .layoutWeight(1)
      }
    }
    
  • 注意:记得给List高度,否则也一样无法滚动。我们这里依然是让它占用剩余高度

  • 这个时候,我们的新年目标列表即具备滚动功能了,大家可以自行添加新目标任务直到超出显示范围,再试试看是否具备滚动到底的效果

知识点 - @Builder装饰器

接下来要讲的侧滑用的上它,所以先对它进行介绍

  • @Builder是用来装饰函数的,被它装饰的函数称之为自定义构造函数

  • 作用:有些时候,一段UI元素可能需要被复用。即可使用@Builder装饰的函数进行封装

  • 语法

    // 注意:写到build函数外面(也就是写成员变量的位置)
     // @Builder也可以跟函数名同一行,但是鸿蒙语法规范建议写到上面
    函数名 (参数) {
         
      // 封装的UI元素
    }
    
  • 例:

     
    myUI () {
         
      Row() {
         
        Text('猫林')
          .fontSize(20)
          .fontWeight(700)
        Button('点赞')
      }
      .width('100%')
      .justifyContent(FlexAlign.Center)
      .backgroundColor(Color.Red)
    }
    
  • 使用时直接像调用函数一样用即可,例如

    build () {
         
    
     Column({
          space: 20 }) {
         
      this.myUI()
    
      this.myUI()
     }
    }
    
  • 效果如下

    image-20240815000556676

  • 跟组件的区别:

    • @Builder更轻量,一般只封装本页面里的UI元素。而组件可能有自己的状态数据且能复用在多个页面

年度目标案例 - 实现侧滑删除

  • 首先,我们需要给每一项加侧滑功能

  • 上面讲解List时,已经说过ListItem可以方便添加侧滑功能

  • 实现方式也很简单,就是给ListItem添加swipeAction属性即可

  • 用法:

    ListItem() {
         
      .....
    }
    .swipeAction( {
          start: 自定义构造函数, end: 自定义构造函数 } )
    
    • start:设置左侧侧滑

    • end:设置右侧侧滑

    • 可以同时写,代表左侧、右侧都具备侧滑效果(即可以左滑,也可以右滑)

    • 也可以根据业务需求,决定单独要哪边的侧滑,像本案例仅需要右侧的侧滑,因此写end即可

    • 自定义构造函数是用来传入侧滑出来的小界面,例如我们本案例右侧出来的部分即是一个小界面,如下图

      image-20240815001042678

    • 那如何把这个小界面传递给ListItem呢?就是通过自定义构造函数传入,也就是用@Builder封装的函数

  • 完成功能:

    • 先来定义侧滑出来的小界面(来到TodoMain

        
        endSwipe() {
             
          Image($r('app.media.ic_public_delete_filled'))
            .width(35)
            .height(35)
            .fillColor(Color.Red) // 如果图片是svg格式的,用这个属性可以改变图片颜色
        }
      
      • ic_public_delete_filled这张图片格式为svg,并且原本是黑色,但我们界面需求是红色,因此可以通过fillColor属性进行改变颜色。(只对svg格式图片有效)
    • 然后给ListItem添加SwipeAction属性,并给end属性(因为需要右侧出现),然后传入上面的自定义构造函数

          List({
              space: 10 }) {
             
            ForEach(this.todoList, (item: TodoModel, index: number) => {
             
              ListItem() {
             
                          .......
              }
              .swipeAction({
              end: this.endSwipe() })
            })
          }
      
    • 经过以上两步后,我们拖拽每一项往左滑动即可出现红色的删除图标,但此时点击图片没有任何反应

    • 所以,我们还需要给@Builder里的删除图标加点击事件,但此时问题来了。点击事件里我们需要:点哪行的删除,就把这行删掉,也即把数组里这一行的数据删除。所以,我们还需要给@Builder的函数传入当前这行的下标当参数,如下

            ForEach(this.todoList, (item: TodoModel, index: number) => {
             
              ListItem() {
             
                ......
              }
              .swipeAction({
              end: this.endSwipe(index) }) // 传入下标
            })
      
    • 最后回到@Builder,添加形参接收,并完成点击事件即可

        
        endSwipe(index: number) {
              // 添加形参
          Image($r('app.media.ic_public_delete_filled'))
            .width(35)
            .height(35)
            .fillColor(Color.Red)
            .onClick(() => {
              // 添加点击事件
                      this.todoList.splice(index, 1)
            })
        }
      
  • 因为之前解释过,todoList是用@Link装饰,所以TodoMain里改变了,也会影响到 Index,既而影响到其他用到同数据的地方
  • 所以到此为止,本案例算完整结束

总结

  • 用文字讲解案例着实麻烦。因为需要大量截图、以及代码解释。以后如果要分享实战项目,猫林老师还是尽量以视频教学为主吧。
  • 到本篇文章结束,如果各位都有每篇认真阅读,其实就已经具备了入门能力。以后几乎遇到任何需求的案例和交互都能实现。
  • 所以后续,猫林老师打算后续的系列文章,都是以分享某一个实战技巧、技术点的形式来写。例如:如何发送请求、如何沉浸式布局、如何多端适配、如何性能分析与优化等,敬请期待
相关文章
|
5天前
|
调度 云计算 芯片
云超算技术跃进,阿里云牵头制定我国首个云超算国家标准
近日,由阿里云联合中国电子技术标准化研究院主导制定的首个云超算国家标准已完成报批,不久后将正式批准发布。标准规定了云超算服务涉及的云计算基础资源、资源管理、运行和调度等方面的技术要求,为云超算服务产品的设计、实现、应用和选型提供指导,为云超算在HPC应用和用户的大范围采用奠定了基础。
179570 18
|
12天前
|
存储 运维 安全
云上金融量化策略回测方案与最佳实践
2024年11月29日,阿里云在上海举办金融量化策略回测Workshop,汇聚多位行业专家,围绕量化投资的最佳实践、数据隐私安全、量化策略回测方案等议题进行深入探讨。活动特别设计了动手实践环节,帮助参会者亲身体验阿里云产品功能,涵盖EHPC量化回测和Argo Workflows量化回测两大主题,旨在提升量化投研效率与安全性。
云上金融量化策略回测方案与最佳实践
|
14天前
|
人工智能 自然语言处理 前端开发
从0开始打造一款APP:前端+搭建本机服务,定制暖冬卫衣先到先得
通义灵码携手科技博主@玺哥超carry 打造全网第一个完整的、面向普通人的自然语言编程教程。完全使用 AI,再配合简单易懂的方法,只要你会打字,就能真正做出一个完整的应用。
9197 23
|
18天前
|
Cloud Native Apache 流计算
资料合集|Flink Forward Asia 2024 上海站
Apache Flink 年度技术盛会聚焦“回顾过去,展望未来”,涵盖流式湖仓、流批一体、Data+AI 等八大核心议题,近百家厂商参与,深入探讨前沿技术发展。小松鼠为大家整理了 FFA 2024 演讲 PPT ,可在线阅读和下载。
4885 12
资料合集|Flink Forward Asia 2024 上海站
|
18天前
|
自然语言处理 数据可视化 API
Qwen系列模型+GraphRAG/LightRAG/Kotaemon从0开始构建中医方剂大模型知识图谱问答
本文详细记录了作者在短时间内尝试构建中医药知识图谱的过程,涵盖了GraphRAG、LightRAG和Kotaemon三种图RAG架构的对比与应用。通过实际操作,作者不仅展示了如何利用这些工具构建知识图谱,还指出了每种工具的优势和局限性。尽管初步构建的知识图谱在数据处理、实体识别和关系抽取等方面存在不足,但为后续的优化和改进提供了宝贵的经验和方向。此外,文章强调了知识图谱构建不仅仅是技术问题,还需要深入整合领域知识和满足用户需求,体现了跨学科合作的重要性。
|
26天前
|
人工智能 自动驾驶 大数据
预告 | 阿里云邀您参加2024中国生成式AI大会上海站,马上报名
大会以“智能跃进 创造无限”为主题,设置主会场峰会、分会场研讨会及展览区,聚焦大模型、AI Infra等热点议题。阿里云智算集群产品解决方案负责人丛培岩将出席并发表《高性能智算集群设计思考与实践》主题演讲。观众报名现已开放。
|
14天前
|
人工智能 容器
三句话开发一个刮刮乐小游戏!暖ta一整个冬天!
本文介绍了如何利用千问开发一款情侣刮刮乐小游戏,通过三步简单指令实现从单个功能到整体框架,再到多端优化的过程,旨在为生活增添乐趣,促进情感交流。在线体验地址已提供,鼓励读者动手尝试,探索编程与AI结合的无限可能。
三句话开发一个刮刮乐小游戏!暖ta一整个冬天!
|
13天前
|
消息中间件 人工智能 运维
12月更文特别场——寻找用云高手,分享云&AI实践
我们寻找你,用云高手,欢迎分享你的真知灼见!
1025 68