测试板rk3588,arkui-x API Version 12

首先一些概念可参考华为官方文档:焦点导航-人机交互 - 华为HarmonyOS开发者

1、焦点变换的方案

                  .id(info.appName)
                  .onFocus(() => {
                    focusControl.requestFocus(info.appName)
                    this.isFocusItem[this.lastFocusIndex] = false
                    this.isFocusItem[info.index] = true
                    this.lastFocusIndex = info.index
                  })
                  .onHover((isHover?: boolean) => {
                    if (isHover) {
                      focusControl.requestFocus(info.appName)
                      this.isFocusItem[this.lastFocusIndex] = false
                      this.isFocusItem[info.index] = true
                      this.lastFocusIndex = info.index
                      // console.log("---AllAppPage", "on hover, index: " + info.index)
                    }
                  })

this.isFocusItem记录列表里每一个iterm是否为焦点。实际

.id用来与focusControl.requestFocus配合使用。

这样的方案既支持鼠标悬停触发变焦,也支持tab键+方向键等键盘操作。

在鼠标接pc和鼠标接测试板的两种场景,鼠标和键盘操作都能够做到正常变焦。

这里的方案也填了前面一篇博文的坑:arkui-x开发中遇到的各种奇怪focus问题_arkui-x 坑-CSDN博客

2、焦点变换的效果以及带来的问题

焦点变换的样式可参考焦点-遥控器及焦点-智慧屏 - 华为HarmonyOS开发者

官方文档里提到了动画效果,本篇博文暂不谈动画这块,后面博文再讲。

这里的示例只讲放大比例105%如何实现,和引入的情况。

2.1 attributeModifier实现边框效果

实现方式,采用attributeModifier可以动态设置组件的属性,根据不同状态调整组件属性。

// focusStyle.ets
export class focusBorderNormal implements AttributeModifier<CommonAttribute> {
  applyNormalAttribute(instance: CommonAttribute): void {
    instance.border({
      width: 0,
      color:Color.Transparent,
      radius: getLength(Const.DEFAULT_5),
    })
  }
}

export class focusThirdPartyInfoBorderRd28 implements AttributeModifier<CommonAttribute> {
  displayInfo: DisPlayInfo = new DisPlayInfo()
  applyNormalAttribute(instance: CommonAttribute): void {
    instance.border({
      width: this.displayInfo.getHeight(4),
      color: Color.White,
      radius: this.displayInfo.getHeight(Const.DEFAULT_28),
    })
    instance.padding(this.displayInfo.getHeight(Const.DEFAULT_4))
  }
}

// example.ets
......
  private modifier: focusThirdPartyInfoBorderRd28 = new focusThirdPartyInfoBorderRd28()
  private modifierNormal: focusBorderNormal = new focusBorderNormal()

......
    Stack() {
    }
    .attributeModifier(this.isFocusItem[info.index] ? this.modifier : this.modifierNormal)
......

applyNormalAttribute(instance: CommonAttribute)可以说是一种封装性、复用性很好的重构方法。比起@extend、@style,它可以方便的支持跨对象复用。相关用法也有很多,后面有机会再研究。

这里的用法,表示this.isFocusItem[info.index]为true,即焦点激活,则this.modifier样式会被使用。其对应的focusThirdPartyInfoBorderRd28表示会在iterm外面padding间隔4px处,增加一个宽4px、border radius为28px的边框,如下图,焦点选中了HelloWorld对应的iterm:

2.2 选中iterm变大5%

参考如下写法,焦点选中时,背景色变大5%,图标变大10%。

Stack() {
  Text()
    .height(135 + (this.isFocusItem[info.index] ? 7 : 0))
    .width(241 + (this.isFocusItem[info.index] ? 12 : 0))
    .borderRadius(24)
    .backgroundColor('#' + info.mainColor)

  Image($r(info.appIcon) : 'file://' + info.appIcon)
    .height(67 + (this.isFocusItem[info.index] ? 7 : 0))  // 放大10%
    .width(85 + (this.isFocusItem[info.index] ? 9 : 0))
    .objectFit(ImageFit.ScaleDown)
}

带来的问题:

页面布局采用的是iterm之间相对布局,用margin来定义,如下:

.margin({ right: 4, bottom: 22 })

当焦点激活时,因为在iterm外增加了一个边框,且iterm变大5%,会导致挤压旁边的iterm,通过margin推动周围iterm,导致联动性的,所有iterm都移动。

3、引入stack叠层方便解决布局联动问题

思路很简单,就是用stack定义一个更大的text,能包含变大的iterm + 边框,那么焦点激活时,就只在这个text内缩放,不会挤压推动周围的iterm。

如下,在Column、Stack组件外面,又增加了一层Stack,其下第一个text就是网格框。

这里需要根据具体布局来计算下网格框大小。height 135后面的16是applyNormalAttribute带来的边框,7为5%的放大值。

          List() {
            ForEach(infoList, (info: ThirdPartyAppInfo) => {
              ListItem() {
                Stack() {
                  Text()  // 此为网格框,保证focus放大效果不超出网格
                    .height(135 + 16 +7) // 7为135的5%
                    .width(241 + 16 + 12) // 12同上
                    .focusable(false)

                  Column() {
                    Stack() {
                      Text()
                        .height(135 + (this.isFocusItem[info.index] ? 7 : 0))
                        .width(241 + (this.isFocusItem[info.index] ? 12 : 0))
                        .borderRadius(24)
                        .backgroundColor('#' + info.mainColor)

                      Image('file://' + info.appIcon)
                        .height(67 + (this.isFocusItem[info.index] ? 7 : 0))  // 放大10%
                        .width(85 + (this.isFocusItem[info.index] ? 9 : 0))
                        .objectFit(ImageFit.ScaleDown)
                    }
                    .attributeModifier(this.isFocusItem[info.index] ? this.modifier : this.modifierNormal)
                    ......
                  }
                  .id(info.appName)
                  .onFocus(() => {
                    focusControl.requestFocus(info.appName)
                    this.isFocusItem[this.lastFocusIndex] = false
                    this.isFocusItem[info.index] = true
                    this.lastFocusIndex = info.index
                  })
                  .onHover((isHover?: boolean) => {
                    if (isHover) {
                      focusControl.requestFocus(info.appName)
                      this.isFocusItem[this.lastFocusIndex] = false
                      this.isFocusItem[info.index] = true
                      this.lastFocusIndex = info.index
                    }
                  })
                  ......
                }
                .margin({ right: 4, bottom: 22 })
              }
            })
          }

视频效果:

2025-02-04-23-13-03-focus效果

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐