您的当前位置:首页正文

HarmonyOS 实战开发 —— 基于原生能力的组件封装

2024-11-25 来源:个人技术集锦

?往期笔录记录?:

?
?
?
?
?
?
?
?


场景描述

在开发应用程序时,我们经常需要使用各种组件来构建和展示用户界面。然而,直接使用原生的组件可能会导致代码冗余和难以维护。为了解决这个问题,基于原生能力的组件封装,将使我们更高效地开发应用程序。

场景一:全局扩展和全局样式使用

在应用开发中,我们通常需要使用相同功能和样式的ArkUI组件,例如购物页面中会使用相同样式的Button按钮、Text显示文字,我们常用的方法是抽取公共样式或者封装成一个自定义组件到公共组件库中以减少冗余代码。

方案

ArkUI提供attributeModifier动态属性设置。

核心代码

1.自定义class实现Button的AttributeModifier接口,通过传入instance参数类型ButtonAttribute识别组件为Button类型,基于instance设置属性值,Button组件的该属性即生效。

// 自定义class实现button的AttributeModifier接口
export class ButtonModifier implements AttributeModifier<ButtonAttribute> {
  // 通过ButtonAttribute标识进行属性设置的组件的类型为button组件
  applyNormalAttribute(instance: ButtonAttribute): void {
    instance.height($r('app.float.float_30'));
    instance.width($r('app.float.float_90'));
    instance.linearGradient({
      angle: LINEAR_ANGLE,
      colors: [[$r('app.color.buttonColor'), 0.5], [$r('app.color.orange'), 1.0]]
    });
  }

2.自定义class实现Row组件的AttributeModifier接口,通过传入instance参数类型RowAttribute识别组件为Row类型,基于instance设置属性值,Row组件的该属性即生效。

// 自定义class实现row组件的AttributeModifier接口
export class BarModifier implements AttributeModifier<RowAttribute> {
  applyNormalAttribute(instance: RowAttribute): void {
    instance.height($r('app.float.float_60'));
    instance.width($r('app.string.max_size'));
    instance.padding($r('app.float.float_15'));
    instance.backgroundColor($r('app.color.barColor'));
    instance.justifyContent(FlexAlign.End);
    instance.border({
      width: { top: $r('app.float.float_1') },
      color: { top: $r('sys.color.ohos_id_color_component_normal') },
    });
  }
}

3.自定义组件对应modifier,并且组件基于attributeModifier绑定对应的属性方法。

// 自定义封装底部组件
@Componentexport
struct BottomBar {
  // 定义button组件的Modifier
  @State buttonModifier: AttributeModifier<ButtonAttribute> = new ButtonModifier();
  // 定义row组件的Modifier
  @State barModifier: AttributeModifier<RowAttribute> = new BarModifier();
  @State buttonName: Resource = $r('app.string.settlement');
  @State barType: BarType = BarType.SHOPPING_CART;
 
  build() {
    Row() {
      if (this.barType === BarType.DETAILS) {
        Button($r('app.string.add_cart'))
          // 绑定对应的动态属性方法
          .attributeModifier(this.buttonModifier)
          .margin({ right: $r('app.float.float_10') })
      }
      Button(this.buttonName)
        // 绑定对应的动态属性方法
        .attributeModifier(this.buttonModifier)
    }
    .attributeModifier(this.barModifier)
  }
}

// 底部购买区(在页面引用自定义封装组件)
BottomBar({
  buttonModifier: new ButtonModifier(),
  barModifier: new BarModifier(),
  buttonName: $r('app.string.settlement'),
  barType: BarType.DETAILS
})

场景二:全局生命周期监听

在页面中播放视频时,将App切换至后台或者关闭屏幕,此时视频会暂停播放,切换到前台想恢复继续播放。

方案

  • 方案一:observer.on(‘navDestinationUpdate’)提供UI组件行为变化的无感监听能力。
  • 方案二:通过字段传递生命周期。
  • 方案三:组件可见区域变化事件去监听变化。

核心代码

方案一:@ohos.arkui.observer (无感监听),通过NavDestinationInfo,获取NavDestination组件信息,来判断当前组件状态,以此来控制视频的播放。

import observer from '@ohos.arkui.observer';
 
// 通过监听当前state状态来执行相应操作
@State @Watch('states') ste: number = 0
states() {
  this.controller.start()
}
 
// 在aboutToAppear生命周期中获取当前NavDestination组件状态
aboutToAppear()
observer.on('navDestinationUpdate', (info) => {
  this.ste = info.state
});
}

方案二:子组件的生命周期无法感应前后的切换,通过onPageShow生命周期来感知页面状态,通过变量传递来通知子组件执行相应的操作。

// 在子组件中通过@Prop来接受父组件的值
// 通过监听isPlayer值的变化来判断前后台的切换
@Prop @Watch('changeVideo')isPlayer: boolean = false
changeVideo() {
  if (this.isPlayer) {
    this.controller.start()
    console.log('console');
  } else {
    this.controller.pause()
  }
}

// 通过onPageShow生命周期来感知页面状态,通过变量传递来通知子组件执行相应的操作
@StateisPlayer: boolean = false
onPageShow():
void {
  this.isPlayer = true
}
onPageHide():
void {
  this.isPlayer = false
}

方案三:onVisibleAreaChange:提供了判断组件是否完全或部分显示在屏幕中的能力,通过这个来感知该组件显示隐藏状态。

// 通过感知组件在屏幕中的显示区域面积变化触发事件
.onVisibleAreaChange([0.0, 1.0], (isVisible: boolean, currentRatio: number) => {
  if (isVisible && currentRatio >= 1.0) {
    this.controller.start()
  }
  if (!isVisible && currentRatio <= 0.0) {
    this.controller.pause()
  }
})

场景三:组件级Storage

银行转账界面,从首页进入转账界面,在此页面执行转账100操作,跳转至账户信息页面显示账户余额为0 。 返回到转账界面之后,变量UI同步刷新显示账户余额0。

方案

可以通过传递不同的LocalStorage实例给自定义组件,从而实现在Navigation跳转到不同的页面时,绑定不同的LocalStorage实例,显示对应绑定的值。

核心代码

1.转账首页构造LocalStorage实例(余额:balance)。

// 构造LocalStorage实例(余额:balance)
let localStorageA: LocalStorage = new LocalStorage();
localStorageA.setOrCreate('balance', 100);
// 使LocalStorage可从@Component组件访问
@Entry(localStorageA)

2.子组件基于@LocalStorageLink变量装饰器与LocalStorage中的’balance’属性建立双向绑定,子组件即可使用balance属性。

// 构造LocalStorage实例(转账金额:transferred)
let localStorageB: LocalStorage = new LocalStorage();
localStorageB.setOrCreate('transferred', 0);
@Component
export struct Second {
  // @LocalStorageLink变量装饰器与LocalStorage中的'balance'属性建立双向绑定
  @LocalStorageLink('balance') balance: number = 100;
  Button('提交').type(ButtonType.Normal)
  .onClick(()=>{
  // 余额减去金额
  this.balance -= this.transferred
  this.pageInfos.pushPath({ name: 'Third' })
})
}
@Component
struct FormItem {
  // @LocalStorageLink变量装饰器与LocalStorage中的'transferred'属性建立双向绑定
  @LocalStorageLink('transferred') transferred: number = 0;
  build() {
    Column() {
      TextInput({ text: this.value, placeholder: this.placeholder })
        .onChange((value) =>{
          // 转账金额赋值
          this.transferred = Number(value)
        })
    }.alignItems(HorizontalAlign.Start)
  }
}

3.账户显示页面使用。

@Component
export struct Third {
  // @LocalStorageLink变量装饰器与LocalStorage中的'transferred'属性建立双向绑定
  @LocalStorageLink('balance') balance: number = 100;
 
  ListItem() {
    // 使用变量
    FormItem({ label: '账户余额', placeholder: '余额' + this.balance })
  }
}

场景四:自定义组件属性的扩展和传递

应用中可基于自定义modifier扩展属性,基于@link传递属性值。如在选购商品时页面,选中之前和选中之后的样式表现不同。

方案

商品页面的文本组件Text并没有设置边框、背景颜色属性。通过自定义modifier为文本组件扩展设置边框、背景颜色、边框颜色属性。选择颜色、尺寸时,选择前后会表现不同的样式。

核心代码

1.自定义modifier继承CommonModifier ,并且定义setGroup2设置组件加载时的属性样式,定义setGroup1设置组件点击触发时的属性样式。

import { CommonModifier } from '@ohos.arkui.modifier';
 
class MyModifier extends CommonModifier {
  applyNormalAttribute(instance: CommonAttribute): void {
    super.applyNormalAttribute?.(instance);
  }
 
  // 组件点击时触发的样式
  public setGroup1(): void {
    // 边框颜色
    this.borderColor($r('app.color.click_color'))
    // 边框宽度
    this.borderWidth(1)
    // 背景颜色
    this.backgroundColor($r('app.color.click_bg_color'))
  }
 
  // 组件加载时的样式
  public setGroup2(): void {
    this.backgroundColor($r('app.color.start_color'))
  }
}

2.自定义modifier内设置width、height值,组件onClick事件内调用setGroup1,且Modifier使用as作为类型断言。

@Entry
@Component
struct Index {
  // 自定义modifier,并设置width、height属性值
  @State myModifier: CommonModifier = new MyModifier().width(100).height(100).margin(10)
 
  build() {
    MyText({ modifier: this.myModifier })// click事件内调用setGroup1
      .onClick(() => {
        // modifier使用as作为类型断言
        (this.myModifier as MyModifier).setGroup1()
      })
  }
}

3.基于@link双向数据绑定,text组件绑定动态属性方法。

@Component
struct MyText {
  @Link modifier: CommonModifier
  build() {
    Text('灰色')
      .attributeModifier(this.modifier as MyModifier)// 组件加载时样式
      .onAppear(() => {
        (this.modifier as MyModifier).setGroup2()
      })
  }
}
显示全文