HarmonyOS鸿蒙应用开发——ArkUI组件封装最佳实践

文章目录

    • 背景与案例描述
    • 静态注册属性-封装UI组件
    • 动态注册属性-封装UI组件
    • 总结

背景与案例描述

在应用开发中,对一些频繁使用的业务UI组件常常会进行一层封装,提取到公共基础库中实现组件的复用,避免类似的逻辑重复编写,减少代码冗余,从而提高开发效率,同时也降低了业务模块间的耦合,可维护性与扩展性会更强,其他开发者在需要时,只需简单地调用或实现这些组件提供的接口,即可快速完成所需功能的开发,很大程度上可以提高团队的效率和代码质量。

在ArkUI中定义一个组件是很简单的,通过@Component装饰器、struct关键字修饰即可,如下:

@Component
export struct Toolbar {
    build(){
    
    }
}

下面写一个Toolbar组件为例,如何循序渐进封装一个UI组件,做到灵活定制,可复用性强的组件,效果图:

在这里插入图片描述

Toolbar组件中有四个元素,分别是返回、标题、分享、更多,其中标题是Text组件,其他元素是Image组件实现,是可点击的。

静态注册属性-封装UI组件

首先定义对外的成员属性,用@Prop装饰器声明用于接收父组件的参数。

@Component
export struct Toolbar {
  // 注释1                
  @Prop title: string | ResourceStr
  @Prop titleColor: ResourceStr | Color
  @Prop titleSize: ResourceStr | number
  // 注释2
  onBack: Function = () => {
  }
  onMore?: () => void
  onShare?:() => void
 } 
  • 注释1:对外的公开属性,接收来自父组件的参数
  • 注释2:点击事件的回调方法,让定制业务逻辑交给使用者去实现。

然后在build()方法中实现UI布局,如下:

  build() {
    Row() {
      Image($r('app.media.ic_toolbar_back'))
        // 注释1
        .imageStyle()
        .onClick(() => {
          this.onBack()
        })
      
      Text(this.title)
        .textAlign(TextAlign.Start)
        .layoutWeight(1)
        .fontColor(this.titleColor)
        .padding({ left: '10vp', right: '10vp' })
        .fontSize(this.titleSize)

      Image($r('app.media.ic_toolbar_share'))
        // 注释1
        .imageStyle()
        .onClick(()=>{
          if (this.onShare) this.onShare()
        })

      Image($r('app.media.ic_toolbar_more'))
        // 注释1
        .imageStyle()
        .margin({ left: '12vp' })
        .onClick(()=>{
          if (this.onMore) this.onMore()
        })

    }
    .backgroundColor(Color.Pink)
    .padding({ left: '10vp', right: '15vp' })
    .height('50vp')
    .width('100%')
  }

注释1处是Image组件的公共样式,通过@Extend装饰器进行了抽离成函数,其函数必须定义在文件顶层作用域中,不能在类中定义。

@Extend(Image)
function imageStyle(){
  .size({ height: '100%', width: '25vp' })
  .objectFit(ImageFit.Auto)
}

上面就完成了Toolbar组件常规封装了,导入使用传入参数。

 Toolbar({
        title: '标题',
        titleSize: '25vp',
        titleColor: Color.Black,
        onBack: () => {
          router.back()
        }
      })

你会发现上面的封装有什么缺陷吗,如果封装组件需要支持样式的动态化时,此时就要在封装组件中新增对应的成员属性,比如在Toolbar组件中,需要修改标题对齐方式、字体粗细等等 ,就需要定义这些特定的属性。如果封装组件是一个组合式组件(由多个组件组合实现),每个类型组件都有自己特有属性,这样整合起来是不是要新增很多成员属性来接收外部的参数呢。

以Text组件的属性为例,如果定义这么多成员属性来接收外部的参数,是一个地狱性的设计。

在这里插入图片描述

这种静态注册属性的方式显然是不太合理的,在处理属性动态化有点力不从心,其扩展性和可维护性差,如果你是封装一个对外开源UI组件,开发者的需求是多样性的,是很难满足个性化需求的。

对此有没更佳的解决方案呢,当然有,系统为每个组件提供一个attributeModifier属性方法,正好可以解决此类的问题。

动态注册属性-封装UI组件

通过AttributeModifier来动态注册属性的方式来封装UI组件,解决静态注册属性的问题,改方式可以将属性从组件分离解耦出来,由外部使用者按需设置。

先初步了解下AttributeModifier接口的属性方法。

在这里插入图片描述

  • applyNormalAttribute:定义组件正常状态的属性值
  • applyPressedAttribute:定义组件按下的属性,比如点击事件
  • applyFocusedAttribute:定义焦点相关的属性
  • applyDisabledAttribute:定义组件禁用属性
  • applySelectedAttribute:定义选择属性

接下来通过AttributeModifier来改造Toolbar封装组。

1、在封装组件中定义对应的Modifier属性。

export struct Toolbar2 {
  @Prop title: StringOrNull
  // 注释1
  @Prop titleModifier: AttributeModifierOrNull<TextAttribute>
  @Prop backModifier: AttributeModifierOrNull<ImageAttribute>
  @Prop shareModifier: AttributeModifierOrNull<ImageAttribute>
  @Prop moreModifier: AttributeModifierOrNull<ImageAttribute>

注释1处从上到下分别是标题、返回、分享、更多按钮的AttributeModifier成员属性,接收外部传入的AttributeModifier类实例。其中AttributeModifierOrNull是自定义的别名联合类型。

export type  AttributeModifierOrNull<T> = AttributeModifier<T> | null

2、接着自定义实现类来实现AttributeModifier接口,在实现类可以定义set方法来实现链式调用方式设置属性。这样我们定义一个BaseModifier基础类来实现AttributeModifier接口,主要是为了后期扩展。

class BaseModifier <T> implements AttributeModifier<T> {
  applyNormalAttribute(instance: T): void {
  }
}

然后在封装组件Toolbar中的aboutToAppear()方法中初始化默认值。

如果不初始化默认值,如果外部没有传入对应的Modifier实例就会抛出异常闪退。

  aboutToAppear(): void {
    if (!this.titleModifier) {
      this.titleModifier = new BaseModifier<TextAttribute>()
    }
    if (!this.backModifier) {
      this.backModifier = new BaseModifier<ImageAttribute>()
    }
    if (!this.shareModifier) {
      this.shareModifier = new BaseModifier<ImageAttribute>()
    }
    if (!this.moreModifier) {
      this.moreModifier = new BaseModifier<ImageAttribute>()
    }

  }

最后将成员变量Modifier属性关联到对应的组件中,如下:

 Image($r('app.media.ic_toolbar_back'))
        .imageStyle()
        // 注释1
        .attributeModifier(this.backModifier)

      Text(this.title)
        .textAlign(TextAlign.Start)
        .layoutWeight(1)
        .padding({ left: '10vp', right: '10vp' })
        // 注释1
        .attributeModifier(this.titleModifier)

      Image($r('app.media.ic_toolbar_share'))
        .attributeModifier(this.shareModifier)
        .imageStyle()

      Image($r('app.media.ic_toolbar_more'))
        .imageStyle()
        .margin({ left: '12vp' })
        // 注释1
        .attributeModifier(this.moreModifier)

注释1处便是Modifier属性的设置,建议将设置关联attributeModifie()方法放在组件的最后调用链处,这样外部设置可以覆盖掉组件内的属性值。

3、外部使用者自定义类实现AttributeModifier接口,或者继承BaseModifier基础类。

class TitleModifier implements AttributeModifier<TextAttribute> {
  private _fontWeight?: FontWeight
  private _textAlign?: TextAlign

  public setFontWeight(value: FontWeight): TitleModifier {
    this._fontWeight = value
    return this
  }

  public setTextAlign(value: TextAlign): TitleModifier {
    this._textAlign = value
    return this
  }

  applyNormalAttribute(instance: TextAttribute): void {
    // 设置属性
    instance.fontColor(Color.Blue)
    instance.fontSize('25vp')
    instance.textAlign(TextAlign.Center)
    instance.fontWeight(FontWeight.Bold)
  }
}

class BackModifier extends  BaseModifier<ImageAttribute> {
  private static instance: BackModifier
  public static getInstance(): BackModifier {
    if (BackModifier.instance) {
      return BackModifier.instance
    } else {
      return new BackModifier()
    }
  }
  
  applyPressedAttribute(instance: ImageAttribute): void {
  // 点击返回
    router.back()
  }
}

最后是外部使用TitleModifier和BackModifier,效果图:

在这里插入图片描述

 Toolbar2({
        title: '标题',
        backModifier: BackModifier.getInstance(),
        titleModifier: new TitleModifier()
      })

其中BackModifier是一个单例模式,如果多处调用可以避免创建多个实例带来的性能损耗,在组件数量少可能很难体现这价值,如果是在长列表场景中可能会体现出来。

总结

静态注册属性封装UI组件的特点:使用简单,由于要手动定义属性,导致在可维护性和可扩展性上有很大的局限性,对于简单的UI组件,静态注册可能是可行的封装方式,易用易理解,但是比较复杂的组合式ui组件,尤其是那些对动态设置要求较高的场景时,静态注册就显得不那么适用了,需要定义众多的成员属性,无法根据实际需求灵活地按需注册属性。

动态注册属性封装UI组件的特点:是通过组件的AttributeModifier来实现的,相较于静态注册方式,尽管操作更为复杂,但可以弥补静态注册的缺陷。这种方法可以按需设置属性,从而体现出极高的灵活性和扩展性,充分体现了封装的精髓,同时也遵循了单一职责设计原则,功能清晰划分明了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/679511.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Excel中高级筛选多个条件怎么做?

高级筛选关键点就在条件设置&#xff0c;筛选条件可以设置多行多列&#xff0c;同一行之间的条件是“并且”的关系&#xff0c;同一列之间的条件是“或者”的关系。 我们以筛选厂家通用、大众&#xff0c;在北京、上海、成都&#xff0c;1月的数据为例来演示条件设置 一、按字…

React - 实现走马灯组件

一、实现效果 二、源码分析 import {useRef, useState} from "react";export const Carousel () > {const images [{id: 3, url: https://sslstage3.sephorastatic.cn/products/2/4/6/8/1/6/1_n_new03504_100x100.jpg}, {id: 1, url: https://sslstage2.sephor…

一个月飙升 9k star!打破常规的 git 客户端

作为一名程序员&#xff0c;想必大家每天都要使用 git 来管理自己的代码吧。有些大佬喜欢使用命令行来进行 git 的操作&#xff0c;有些新入门的小白程序员则比较喜欢使用各种 git 客户端来可视化的管理代码&#xff0c;而有些程序员则喜欢使用 IDE 中集成的 git 功能来做代码的…

【机器学习】Softmax回归探索

从零开始探索Softmax回归&#xff1a;深度学习的入门之旅 一、Softmax回归的原理与关键步骤二、研究准备&#xff1a;GPU环境下的PyTorch安装与配置三、研究内容&#xff1a;使用PyTorch实现Softmax回归 随着人工智能和机器学习的迅猛发展&#xff0c;深度学习技术逐渐成为了科…

Codeforces Round 950 (Div. 3)(A~D题)

A. Problem Generator 思路:暴力模拟,对于每个字母&#xff0c;如果不足m mm&#xff0c;就加入最终答案. 实现代码: #include<bits/stdc.h> using namespace std; #define N 2000005 #define mod 100003 typedef long long ll; ll n, m, t, cnt, ans, sum1,sum2, maxx…

史上Z快ST

一支股票ST要多久&#xff0c;锦州港告诉你&#xff0c;3个交易日足矣。上周五发出发公告&#xff0c;今天停牌1天&#xff0c;明天复牌就变ST。对锦州港的九万股民来说&#xff0c;好消息是今天不会跌&#xff0c;坏消息是复牌ST以后可以开始每天数地板了。 又是一个浓眉大眼的…

前端 CSS 经典:3D Hover Effect 效果

前言&#xff1a;有趣的 3D Hover Effect 效果&#xff0c;通过 js 监听鼠标移动&#xff0c;动态赋值 rotateX&#xff0c;rotateY 的旋转度来实现。 效果图&#xff1a; 代码实现&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta …

Python脚手架系列-PyQt5

记录PyQt模块使用中的一些常常复用的代码 其他 导入界面 import sysfrom PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QApplication, QMainWindow from UI.MainWindow import Ui_MainWindow # 导入UI界面的类以供继承class MyApp(QMainWindow, Ui_MainWindow):de…

Scala环境的搭建

要搭建Scala&#xff0c;我们必须先下载java&#xff0c;由于我的电脑已经搭建好了环境&#xff0c;因此我这里用截图来教大家搭建环境。 可以从网上搜索安装包对其进行安装 IntelliJ IDEA – 领先的 Java 和 Kotlin IDE 不建议下载最新版的&#xff0c;大家下载的版本可以下…

CyberDAO引领Web3新时代,共创去中心化未来

Web3的新时代 Web3是互联网的下一代版本&#xff0c;基于区块链技术&#xff0c;实现了去中心化、透明和安全的网络体验。与微信、淘宝等传统中心化平台不同&#xff0c;Web3赋予用户更多的控制权和数据所有权。用户行为数据将由用户自己拥有并分布式管理&#xff0c;不再集中…

PySpark特征工程(III)--特征选择

有这么一句话在业界广泛流传&#xff1a;数据和特征决定了机器学习的上限&#xff0c;而模型和算法只是逼近这个上限而已。由此可见&#xff0c;特征工程在机器学习中占有相当重要的地位。在实际应用当中&#xff0c;可以说特征工程是机器学习成功的关键。 特征工程是数据分析…

LeetCode刷题之最大子数组

今天打算多做一题。 1、题目描述 2、逻辑分析 哈哈&#xff0c;这题我前两天在小红书刷到了&#xff0c;博主答不上来&#xff0c;一样的是&#xff0c;我也不知道怎么做。当时只看到评论说什么dp解法&#xff0c;看看题解怎么说。现在才反应过来dp dynamic programming &am…

【C语言】详解函数(庖丁解牛版)

文章目录 1. 前言2. 函数的概念3.库函数3.1 标准库和头文件3.2 库函数的使用3.2.1 头文件的包含3.2.2 实践 4. 自定义函数4.1 自定义函数的语法形式4.2 函数的举例 5. 形参和实参5.1 实参5.2 形参5.3 实参和形参的关系 6. return 语句6. 总结 1. 前言 一讲到函数这块&#xff…

数据库(19)——字符串函数

函数是指一段可以直接被另一段程序调用的程序代码。 常用的函数 函数功能CONCAT(S1,S2...Sn)字符串拼接LOWER(str)将字符串全部转换为小写UPPER(str)将字符串全部转换为大写LPAD(str,n,pad) 用字符串pad对str的左边进行填充RPAD(str,n,pad)用字符串…

10倍速提升音乐制作,FL Studio21.2.9中文版揭秘!

FL Studio21中文版是数字音频工作站软件领域的一颗璀璨明星&#xff0c;它以强大的功能和直观的操作界面&#xff0c;赢得了音乐制作人和爱好者的广泛青睐。无论是专业音乐人还是初学者&#xff0c;都能通过这款软件探索和实现他们对音乐的创作和想象。本文将详细介绍FL Studio…

Maven实战: 从工程创建自定义archetype

在上一节中(创建自定义archetype)我们手动创建了一个项目模板&#xff0c;经过5步能创建出一个项目模板&#xff0c;如果我有一个现成的项目&#xff0c;想用这个项目作为模板来生成其他项目呢&#xff1f;Maven提供了基于项目生成archetype模板的能力&#xff0c;我们分3步来讲…

公差和配合

配合的选择&#xff1a; 配合特性以及基本偏差的应用&#xff1a; 常用优先配合特性及选用举例 为什么一般情况下选用基孔制而不用基轴制&#xff1a; 优先采用基孔制的原因主要包括工艺性、经济性和标准化&#xff1a; 工艺性。加工孔比加工轴更难&#xff0c;因为孔…

函数计数和跟踪 --- console的count和trace方法

新学到一个小方法&#xff0c;分享一下哦。 使用 console 对象的 trace ⽅法在控制台上输出当前的调用栈&#xff0c;可以追踪⼀个函数的执⾏过程。 当我们想要了解一个函数是如何被其他函数调用的&#xff0c;或者想要查看调用栈中的其他信息时&#xff0c;这个方法非常有用…

韩文图片文字识别,这几款软件轻松驾驭韩语文本

在当今信息爆炸的时代&#xff0c;跨语言交流已成为日常生活和工作中的常态。对于需要处理韩文文本的用户来说&#xff0c;韩文图片文字识别技术无疑是一大福音。今天&#xff0c;就为大家介绍几款优秀的韩文图片文字识别软件&#xff0c;让你轻松驾驭韩语文本&#xff0c;提升…

性能工具之 JMeter 常用组件介绍(二)

文章目录 一、Thread Group二、断言组件1、Response Assertion&#xff1a;响应断言2、Response Assertion&#xff1a;响应断言3、Duration Assertion&#xff1a;响应时间断言4.、JSON Assertion&#xff1a;json断言 一、Thread Group 线程组也叫用户组&#xff0c;是性能测…