OpenHarmony - ArkUI(TS)开发之下拉选择菜单 原创 精华

中软HOS小鸿
发布于 2022-8-23 15:38
438浏览
9收藏

作者:何贝

前言

鸿蒙3.0推出了一种新的开发方式ETS,本文通过采用ETS实现我们项目开发中比较常见的下拉选择菜单组件来初步了解和体验下ETS的开发规范和方法,主要用到的TS知识点有Flex布局、文本展示、样式绑定、图片引入、父子组件变量共享、参数传递、ForEach循环遍历、事件绑定。

实现效果

OpenHarmony - ArkUI(TS)开发之下拉选择菜单-鸿蒙开发者社区

用到的装饰器

装饰器 装饰内容 说明
@Component struct 结构体在装饰后具有基于组件的能力,需要实现build方法来更新UI。
@Entry struct 组件被装饰后作为页面的入口,页面加载时将被渲染显示。
@Builder 方法 在@Builder装饰的方法用通过声明式UI描述,可以在一个自定义组件内快速生成多个布局内容。
@Provide 基本数据类型,类,数组 @Provide作为数据的提供方,可以更新其子孙节点的数据,并触发页面渲染。
@Consume 基本数据类型,类,数组 @Consume装饰的变量在感知到@Provide装饰的变量更新后,会触发当前自定义组件的重新渲染。

创建ETS工程

  1. 在DevEco Studio中新建一个ETS工程,语言选择ets。

OpenHarmony - ArkUI(TS)开发之下拉选择菜单-鸿蒙开发者社区

OpenHarmony - ArkUI(TS)开发之下拉选择菜单-鸿蒙开发者社区

OpenHarmony - ArkUI(TS)开发之下拉选择菜单-鸿蒙开发者社区

  1. 在entry -> src -> main -> ets -> default -> pages目录下新建自己的.ets文件,即可以开始编写ets代码。

OpenHarmony - ArkUI(TS)开发之下拉选择菜单-鸿蒙开发者社区

功能分解

  1. 创建一个Flex布局的卡片
Flex({justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center}) {
}
.height(64)
.width('100%')
.backgroundColor('#fff')
.borderRadius(16)
.padding({
  left: 16,
  right: 16
})
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  1. 在卡片中填充内容
Column() {
    Text('模式选择')
        .fontSize(16)
    Text('清洁模式')
        .fontSize(12)
        .alignSelf(ItemAlign.Start)
        .fontColor('rgba(0, 0, 0, 0.6)')
        .margin({
        top: 2
    })
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  1. 图片资源的引入,icon.png存在resources->base->media目录下
Column() {
	Flex(){
        Image($r('app.media.icon'))
        .objectFit(ImageFit.Contain)
    }
    .height(24)
    .width(24)
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  1. 绑定点击事件
.onClick(() => {
    this.showSelector = !this.showSelector;
    console.log('showSelector==='+this.showSelector)
})
  • 1.
  • 2.
  • 3.
  • 4.
  1. 循环输出标签
ForEach(this.modesData, item => {
    Flex(){
        ModeItem({ mode: item })
    }
    // 为每一项绑定点击事件
    .onClick(() => {
        if (this.modeId !== item.id) {
            this.modeId = item.id
            console.info('this.modeId==='+this.modeId)
        }
        this.showSelector = false
    })

}, item => item.id.toString())
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  1. 父子组件共享变量

父组件中使用@Provide修饰变量,子组件使用@Consume修饰同一个变量名,即可以实现共享。

// 父组件
@Entry
@Component
struct SelectorIndex {
  @Provide modeId: number = 0 // 定义当前选中项id
  build() {
      Flex() {
          SelectorList() // 显示子组件
      }
  }
}
// 子组件
@Component
struct SelectorList {
  @Consume modeId: number // 共享父组件属性
  build() {
      Flex(){
          
      }.onClick(() => {
          console.log(this.modeId) // 打印当前选中项id
      })
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

完整代码

// 数据类型定义
export class ModeType {
  id: number
  name: string
  constructor(id: number, name: string) {
    this.id = id;
    this.name = name;
  }
}
// 单个选择项的渲染
@Component
struct ModeItem {
  private mode: ModeType
  @Consume modeId : number

  @Builder renderModeItem(fontColor: string, bgColor: string, value: string) {
    Flex({direction: FlexDirection.Column}) {
      Flex(){
        Text(value)
          .fontSize(16)
          .fontColor(fontColor)
      }
      .height('100%')
      .width('100%')
      .backgroundColor(bgColor)
      .borderRadius(12)
      .padding({
        left: 14,
        top: 14,
        bottom: 14
      })
      // 最后一项不显示分隔线
      if (this.mode.id != 5) {
        Flex() {
          Flex() {
          }.height(1).width('100%').backgroundColor('#F3F3F3')
        }
        .padding({
          left: 12,
          right: 12
        })
      }
    }
  }
  build() {
    Flex(){
      // 选中项和其他选项的样式有不同
      if (this.modeId == this.mode.id) {
        this.renderModeItem('#0A59F7', '#E6EEFF', this.mode.name)
      } else {
        this.renderModeItem('rgba(0,0,0,0.9)', '', this.mode.name)
      }
    }.height(48)
  }
}
// 下拉菜单组件
@Component
struct SelectorList {
  @Consume modesData: any // 共享父组件属性
  @Consume modeId: number // 共享父组件属性
  @Consume showSelector: boolean // 共享父组件属性
  build() {
    Flex({direction: FlexDirection.Column, justifyContent: FlexAlign.Start}) {
      // 循环产生下拉菜单
      ForEach(this.modesData, item => {
        Flex(){
            ModeItem({ mode: item })
        }
        // 为每一项绑定点击事件
        .onClick(() => {
          if (this.modeId !== item.id) {
            this.modeId = item.id
            console.info('this.modeId==='+this.modeId)
          }
          this.showSelector = false
        })

      }, item => item.id.toString())
    }
    .height(248)
    .width('100%')
    .backgroundColor('#fff')
    .borderRadius(16)
    .shadow({
      radius: 50,
      color: 'rgba(0,0,30,0.1500)'
    })
    .padding({
      left: 4,
      right: 4,
      top: 4,
      bottom: 4
    })
    .position({
      x: 0,
      y: -260
    })
    .zIndex(1)
  }
}
// 入口组件
@Entry
@Component
struct SelectorIndex {
  @Provide showSelector: boolean = false // 是否展开下拉菜单
  @Provide modesData: any = [{id: 1,name: '节能模式'},{id: 2,name: '强劲模式'},{id: 3,name: '深层模式'},{id: 4,name: '清洁模式'},{id: 5,name: '敏感模式'}]
  @Provide modeId: number = 0 // 当前选中项id
  build() {
    Flex({direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center}) {
      Flex({justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center}) {
        Text('其他内容1').fontSize(16)
      }.height(88).width('100%').backgroundColor('#fff').borderRadius(16)

      Flex({direction: FlexDirection.Column, alignItems: ItemAlign.Center}) {
        if (this.showSelector) {
          SelectorList()
        }
        Flex({justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center}) {
          Column() {
            Text('模式选择')
              .fontSize(16)
            if (this.getSelectedText()) {
              Text(this.getSelectedText())
                .fontSize(12)
                .alignSelf(ItemAlign.Start)
                .fontColor('rgba(0, 0, 0, 0.6)')
                .margin({
                  top: 2
                })
            }
          }
          Column() {
            Flex(){
              Image($r('app.media.icon'))
                .objectFit(ImageFit.Contain)
            }
            .height(24)
            .width(24)
          }
        }
        .height(64)
        .width('100%')
        .backgroundColor('#fff')
        .borderRadius(16)
        .padding({
          left: 16,
          right: 16
        })
        .onClick(() => {
          this.showSelector = !this.showSelector;
          console.log('showSelector==='+this.showSelector)
        })

      }
      .height(64)
      .margin({
        top: 12,
        bottom: 12
      })

      Flex({justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center}) {
        Text('其他内容2').fontSize(16)
      }.height(88).width('100%').backgroundColor('#fff').borderRadius(16)
    }
    .height('100%')
    .width('100%')
    .backgroundColor('#f1f3f5')
    .padding({
      left: 12,
      right: 12,
      top: 16,
      bottom: 16
    })
    .onClick(() => {
      this.showSelector = false
    })
  }
  // 获取选中项的内容
  getSelectedText() {
    const selectedItem = this.modesData.find(item => {
      return item.id == this.modeId
    })
    if (selectedItem) {
      return selectedItem.name
    }
    return ''
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.

总结

从上面的示例我们可以感受到基于TS扩展的声明式开发范式的方舟开发框架,采用链式调用的编程方式,可以更直观地描述UI界面,不必关心框架如何实现UI绘制和渲染,另外也提供了丰富的系统能力接口,真正实现极简高效开发。大家感兴趣的话可以通过TS开发官方文档进行详细的了解和学习。华为鸿蒙官方也发布了3 个超级实用的基于 TS 扩展的声明式开发范式示例,分别是eTSBuildCommonView 创建简单视图示例、eTSDefiningPageLayoutAndConnection 页面布局和连接示例、eTSDrawingAndAnimation 绘图和动画示例,可以下载代码体验和学习。

更多原创内容请关注:中软国际 HarmonyOS 技术团队

入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2022-8-23 15:38:45修改
13
收藏 9
回复
举报
13
10
9
10条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

是ETS的内容!前排学习。

回复
2022-8-23 15:58:40
贝贝小跟班
贝贝小跟班

这娃好厉害啊,这娃真棒

回复
2022-8-23 17:23:56
wx6232a29aedab4
wx6232a29aedab4

赞,学习下

回复
2022-8-23 17:29:25
wx62b07722cea3f
wx62b07722cea3f

真棒,很详细,学习学习

回复
2022-8-23 17:36:43
有故事的王同学
有故事的王同学

看了下官方示例还是挺丰富的,期待ETS未来的发展

已于2022-8-23 17:59:01修改
回复
2022-8-23 17:58:51
wzhishun
wzhishun

看这个面板是要弄个空调遥控器?

回复
2022-8-24 17:42:42
诺舒华吃西瓜
诺舒华吃西瓜

不错,TS确实简洁不少

回复
2022-8-25 18:25:47
0aaron
0aaron

看来TS确实要取代JS了

回复
2022-8-26 11:14:55
有故事的王同学
有故事的王同学

回家试试代码,学习一下

回复
2022-8-26 18:23:24
前清秀才
前清秀才

我运行这个代码,怎么报错了, 'SelectorList()' does not comply with the UI component syntax. <ArkTSCheck>

回复
2024-8-13 16:14:33


回复
    相关推荐