从零开始开发纯血鸿蒙应用之UI封装

embedded/2025/1/8 20:13:47/

从零开始开发纯血鸿蒙应用

  • 一、题引
  • 二、UI 组成
  • 三、UI 封装原则
  • 四、实现 lib_comps
    • 1、封装 UI 样式
      • 1.1、attributeModifier 属性
      • 1.2、自定义`AttributeModifier<T>`类
    • 2、封装 UI 组件
  • 五、总结

一、题引

在开始正文前,为了大家能够从本篇博文中,汲取到代码外的东西,即编程思想,我想问问每一个屏幕前的读者,一个问题,那就是:所谓UI,在你看来,可以划分成哪些组成?

二、UI 组成

UI,即 User Interface,译为用户界面,是一个应用提供给用户直接进行查看和操作的内容。
虽然,还不知道大家脑海中对于UI组成,都有什么样的看法?但我这里需要说的是,我认为一个合格的UI,当是由结构样式响应有机组合成的,而合格的UI封装也应该就此进行。

UI 实际上就像大家日常生活中居住的房子,一个住起来感到惬意的房间,必然先后经历房壳子和装潢后的房间,这两个截然不同的阶段;在装潢阶段,房间的墙壁或被贴上墙纸、或被刷上墙漆,墙壁上的某些位置上出现的各种插座、开关面板,则是早在搭建房壳子的时候一并完成的,然而它们实际会控制什么样的电器,则是在装潢阶段进行的。

三、UI 封装原则

再问大家一个问题:进行UI公共组件的封装时,是习惯于将结构、样式和响应,杂糅在一起进行封装,还是采用粒度更细化的方式进行呢?

我进行UI封装时,会有一个很明确的行为准则:单一职能原则,一个封装实现体(class或struct),应该只承担粒度从小到大顺序中的某一个职能,比如只负责确定结构、或只负责确定样式。
当然了,前提是所使用的UI开发框架支持相应的粒度抽取。

就鸿蒙应用SDK来说,最新版本的API里,样式已经允许与结构、响应独立,封装在不同的ets文件中,具体如何实现,后文细说;而对于结构和响应,就像造房子一样,墙壁上开关面板的走线管道,只能在墙壁完整造出来前布置好,UI结构封装时必须预留响应的载入通道,通常就是允许传入一个函数

四、实现 lib_comps

现在,开始对工程里面的 lib_comps 模块进行实现,先看一下该模块下的目录结构:
lib_comps模块目录结构
在 ets 源码目录下,我细分出 componentsstyle两个子目录,前者负责UI结构和响应载入通道的封装,后者负责UI样式的封装。

1、封装 UI 样式

进行网页实现的时候,大家经常会用到类似如下的代码:

<div class="container"><p class="normal-text">文本</p>
</div>

html 标签的 class 属性,用于将提前封装在CSS文件中的各种样式进行载入的位置,只有这样,才可能进行样式的独立封装。现在,我可以明确地告诉大家,鸿蒙API 12 之后也能如此进行了:
在这里插入图片描述

1.1、attributeModifier 属性

在我看来,该属性是相当重要而有用的,它让鸿蒙 UI 实现的灵活度得到了进一步的增强。
在这里插入图片描述
虽然,官方将其解释为“动态设置组件的属性方法”,但我却更愿意将其看待成鸿蒙组件的class属性,因为,在鸿蒙UI实现中,组件样式也是通过属性方法进行设置的,比如下面:

Row(){Text(this.fileName.split(".")[0]).fontSize(25).fontWeight(FontWeight.Regular)Text(this.fileName.split(".")[1]).fontSize(20).fontWeight(FontWeight.Lighter)Text('操作').fontSize(25).fontWeight(FontWeight.Regular).bindMenu(this.OptionMenu())}.width("100%").height("10%").alignItems(VerticalAlign.Center).justifyContent(FlexAlign.SpaceBetween)

那么,传入 attributeModifier 属性里面的自定义AttributeModifier<T>类里面,只设置样式相关的属性,那么,实际上对应的自定义AttributeModifier<T>类就相当是一个css了。

1.2、自定义AttributeModifier<T>

实现时,要与目标组件的类型相一致,也就是说,如果是要为 column 组件的 attributeModifier 属性赋值,那么泛类型 T 对应的具体类型就是 ColumnAttribute。

通常,应用里面使用的 column 样式不会只有一种,所以,不妨把所有适用于 column 的样式封装,都放在同一份 ets 文件中,从而就有了前面 lib_comps 工程目录结构图里面的 ColumnStyles.ets 文件。

下面,以 RootTopColumn 类为例,讲解如何进行自定义AttributeModifier<T>类的封装:

export class RootTopColumn implements AttributeModifier<ColumnAttribute> {private columnColor: ResourceColor;constructor(columnColor: ResourceColor) {this.columnColor = columnColor;}applyNormalAttribute(instance: ColumnAttribute): void {instance.width("100%").height("100%").backgroundColor(this.columnColor).alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Start)}applyPressedAttribute(instance: ColumnAttribute): void {}applyFocusedAttribute(instance: ColumnAttribute): void {}applyDisabledAttribute(instance: ColumnAttribute): void {}applySelectedAttribute(instance: ColumnAttribute): void {}}

首先,自定义AttributeModifier<T>类和普通类一样,可以定义字段并通过构造函数进行初始化或赋值;其次,可以一次性封装5种不同的样式,用以针对组件的普通状态、按下状态、获焦状态、不可用状态和选择状态,不同的状态样式由各自对应的 applyXXXXAttribute 方法实现,这些方法统一接受一个 T 类型参数,对于 Column 组件来说,就是 ColumnAttribute;最后,在具体渲染时调用哪一个 applyXXXXAttribute 方法,由 UI 渲染引擎自行判断,开发者无需关心。

实现每个具体的 applyXXXXAttribute 方法时,可以从样式的如下几方面进行:

  • 尺寸:width 属性和 height 属性
  • 颜色:背景色 backgroundColor 属性,前景色foregroundColor属性
  • 对齐方式:alignItem 属性和 justifyContent 属性
  • 边距:外边距 margin 属性和内边距 padding 属性
  • 边框:boder 属性

当然了,鸿蒙 UI 组件支持的样式属性绝不止这些,只不过上面这些是比较常用的,基本足够应付大多数UI实现所需的样式了。

当对应的 自定义AttributeModifier<T>类,需要用在最外层的容器上,那么尺寸就应该是与屏幕尺寸相同,这里的 RootTopColumn 就是这样的,所以,我才会在 applyNormalAttribute 函数中,将尺寸都设置为百分百;同时,因为预期布局方式是水平居中、垂直局始,所以,只剩下背景色需要通过类构造函数进行传入。

2、封装 UI 组件

由于,鸿蒙API中,UI 结构和响应,没办法彻底解耦,所以,结构和响应就没有按照彻底独立的方式进行封装,而是直接上升到组件维度进行封装。

在组件中,按钮和对话框应该算得上是使用频率很高的组件了,所以,有必要为此封装相应的公共组件,以黑色背景的按钮来说,可以用类似如下的代码进行封装:

@Component
export struct BlackBtn {private buttonWidth: Length = "75%"private buttonHeight: Length = 55@Prop @Require text: ResourceStr = ""private textSize: uiltin">string | uiltin">number | Resource = 20private action?: () => void = () => {}private btnStyle: BlackButton = new BlackButton(this.buttonWidth, this.buttonHeight)build() {Button() {Text(this.text).WhiteText(this.textSize)}.attributeModifier(this.btnStyle).onClick(this.action)}
}

在鸿蒙UI中,所有的组件都是用注解了 Component 的 struct 体承担实现,并且该 struct 体中必须实现 build 方法,具体的 UI 内容实现放在 build 方法里,build 方法里不允许使用非UI表达式,除了少数允许的条件表达式外的一切逻辑表达式,都不允许直接放在 build 方法体里面。

由于注解了 Component 的 struct 体,跟类一样,允许从外部传入参数,所以,具体的内容和样式参数,都可以从外部传入;当然了,也可以为它们都设置初始化值;如果某个属性值必须要外部传入,可以在相应的字段前用 Require 注解。

对话框的封装,也是差不多的形式:
在这里插入图片描述
从上面的代码可以看出,封装UI组件的时候,可以根据内容的多寡,决定是否继续划分出更小的组件。

五、总结

受限于篇幅,我这里并没有将 lib_comps 模块的全部实现代码贴出来,大家不妨前往lib_comps仓库,查看具体的实现代码。

总而言之,在封装UI的时候,应当在遵循单一职能的基础上,按粒度从小到大的形式进行内容拆分和实现,一块砖一块砖地码,万里长城才能码得稳、码得好!


http://www.ppmy.cn/embedded/151292.html

相关文章

电化学气体传感器在物联网中的精彩表现

电化学气体传感器是一种基于电化学原理来检测气体浓度的设备。它通过测量目标气体在电极处氧化或还原而产生的电流或电势信号&#xff0c;从而推算出气体的浓度。这种工作原理使得它可被用于探测大多数的有毒有害气体&#xff0c;包括一氧化碳、硫化氢、氯气、二氧化硫等。和武…

APM for Large Language Models

APM for Large Language Models 随着大语言模型&#xff08;LLMs&#xff09;在生产环境中的广泛应用&#xff0c;确保其可靠性和可观察性变得至关重要。应用性能监控&#xff08;APM&#xff09;在这一过程中发挥了关键作用&#xff0c;帮助开发者和运维人员深入了解LLM系统的…

快速了解缓存穿透与缓存雪崩

在缓存系统的使用过程中&#xff0c;缓存穿透和缓存雪崩是两种常见的问题&#xff0c;它们会导致缓存失效&#xff0c;从而对系统性能造成影响。下面我将快速介绍这两个问题及其解决方法。 1. 缓存穿透 (Cache Penetration) 缓存穿透是指客户端请求的某些数据&#xff0c;既不…

LeetCode算法题——移除元素

题目描述 给你一个数组 nums 和一个值 val&#xff0c;你需要原地移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。 假设 nums 中不等于 val 的元素数量为 k&#xff0c;要通过此题&#xff0c;您需要执行以下操作&#xff1…

【Python】selenium结合js模拟鼠标点击、拦截弹窗、鼠标悬停方法汇总(使用 execute_script 执行点击的方法)

我们在写selenium获取网络信息的时候&#xff0c;有时候我们会受到对方浏览器的监控&#xff0c;对方通过分析用户行为模式&#xff0c;如点击、滚动、停留时间等&#xff0c;网站可以识别出异常行为&#xff0c;进而对Selenium爬虫进行限制。 这里我们可以加入JavaScript的使…

HTML——16.相对路径

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title></title></head><body><a href"../../fj1/fj2/c.html" target"_blank">链接到c</a><!--相对路径&#xff1a;-->…

Flume的安装和使用

一、安装Flume 1. 下载flume-1.7.0 http://mirrors.shu.edu.cn/apache/flume/1.7.0/apache-flume-1.7.0-bin.tar.gz 2. 解压改名 tar xvf apache-flume-1.7.0-bin.tar.gz mv apache-flume-1.7.0-bin flume 二、配置Flume 1. 配置sh文件 cp conf/flume-env.sh.template …

UML之组合与聚合

关联和链接关系在很多情况下是对称的&#xff0c;即被关联的两个类都有以自己为源端对方为目标端的角色存在&#xff0c;而且角色与源端类的属性是等价的&#xff0c;即在关联一端的关联端&#xff08;角色&#xff09;等价于另外一端的属性。例如&#xff0c;在下图中&#xf…