您的位置:首页 > 娱乐 > 八卦 > HarmonyOS鸿蒙应用开发——ArkUI组件封装最佳实践

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

2024/10/31 9:51:17 来源:https://blog.csdn.net/hzw2017/article/details/139444871  浏览:    关键词: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// 注释2onBack: Function = () => {}onMore?: () => voidonShare?:() => 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?: FontWeightprivate _textAlign?: TextAlignpublic setFontWeight(value: FontWeight): TitleModifier {this._fontWeight = valuereturn this}public setTextAlign(value: TextAlign): TitleModifier {this._textAlign = valuereturn 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: BackModifierpublic 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来实现的,相较于静态注册方式,尽管操作更为复杂,但可以弥补静态注册的缺陷。这种方法可以按需设置属性,从而体现出极高的灵活性和扩展性,充分体现了封装的精髓,同时也遵循了单一职责设计原则,功能清晰划分明了。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com