鸿蒙Harmony开发:通用焦点样式事件规范

news2024/11/16 9:41:01

基础概念

焦点、焦点链和走焦

  • 焦点:指向当前应用界面上唯一的一个可交互元素,当用户使用键盘、电视遥控器、车机摇杆/旋钮等非指向性输入设备与应用程序进行间接交互时,基于焦点的导航和交互是重要的输入手段。
  • 焦点链:在应用的组件树形结构中,当一个组件获得焦点时,从根节点到该组件节点的整条路径上的所有节点都会被视为处于焦点状态,形成一条连续的焦点链。
  • 走焦:指焦点在应用内的组件之间转移的行为。这一过程对用户是透明的,但开发者可以通过监听onFocus(焦点获取)和onBlur(焦点失去)事件来捕捉这些变化。关于走焦的具体方式和规则

焦点态

用来指向当前获焦组件的样式。

  • 显示规则:默认情况下焦点态不会显示,只有当应用进入激活态后,焦点态才会显示。因此,虽然获得焦点的组件不一定显示焦点态(取决于是否处于激活态),但显示焦点态的组件必然是获得焦点的。大部分组件内置了焦点态样式,开发者同样可以使用样式接口进行自定义,一旦自定义,组件将不再显示内置的焦点态样式。在焦点链中,若多个组件同时拥有焦点态,系统将采用子组件优先的策略,优先显示子组件的焦点态,并且仅显示一个焦点态。
  • 进入激活态:仅使用外接键盘按下TAB键时才会进入焦点的激活态,进入激活态后,才可以使用键盘TAB键/方向键进行走焦。首次用来激活焦点态的TAB键不会触发走焦。
  • 退出激活态:当应用收到点击事件时(包括手指触屏的按下事件和鼠标左键的按下事件),焦点的激活态会退出。

层级页面

层级页面是焦点框架中特定容器组件的统称,涵盖Page、Dialog、SheetPage、ModalPage、Menu、Popup、NavBar、NavDestination等。这些组件通常具有以下关键特性:

  • 视觉层级独立性:从视觉呈现上看,这些组件独立于其他页面内容,并通常位于其上方,形成视觉上的层级差异。
  • 焦点跟随:此类组件在首次创建并展示之后,会立即将应用内焦点抢占。
  • 走焦范围限制:当焦点位于这些组件内部时,用户无法通过键盘按键将焦点转移到组件外部的其他元素上,焦点移动仅限于组件内部。

在一个应用程序中,任何时候都至少存在一个层级页面组件,并且该组件会持有当前焦点。当该层级页面关闭或不再可见时,焦点会自动转移到下一个可用的层级页面组件上,确保用户交互的连贯性和一致性。

说明

Popup组件在focusable属性(组件属性,非通用属性)为false的时候,不会有第2条特性。

NavBar、NavDestination没有第3条特性,对于它们的走焦范围,是与它们的首个父层级页面相同的。

根容器

根容器是层级页面内的概念,当某个层级页面首次创建并展示时,根据层级页面的特性,焦点会立即被该页面抢占。此时,该层级页面所在焦点链的末端节点将成为默认焦点,而这个默认焦点通常位于该层级页面的根容器上。

在缺省状态下,层级页面的默认焦点位于其根容器上,但开发者可以通过defaultFocus属性来自定义这一行为。

当焦点位于根容器时,首次按下TAB键不仅会使焦点进入激活状态,还会触发焦点向子组件的传递。如果子组件本身也是一个容器,则焦点会继续向下传递,直至到达叶子节点。传递规则是:优先传递给上一次获得焦点的子节点,如果不存在这样的节点,则默认传递给第一个子节点。

走焦规范

根据走焦的触发方式,可以分为主动走焦和被动走焦。

主动走焦

指开发者/用户主观行为导致的焦点移动,包括:使用外接键盘的按键走焦(TAB键/Shift+TAB键/方向键)、使用requestFocus申请焦点、clearFocus清除焦点、focusOnTouch点击申请焦点等接口导致的焦点转移。

  • 按键走焦
  1. 前提:当前应用需处于焦点激活态。
  2. 范围限制:按键走焦仅在当前获得焦点的层级页面内进行,具体参见“层级页面”中的“走焦范围限制”部分。
  3. 按键类型:

    TAB键:遵循Z字型遍历逻辑,完成当前范围内所有叶子节点的遍历,到达当前范围内的最后一个组件后,继续按下TAB键,焦点将循环至范围内的第一个可获焦组件,实现循环走焦。

    Shift+TAB键:与TAB键具有相反的焦点转移效果。

    方向键(上、下、左、右):遵循十字型移动策略,在单层容器中,焦点的转移由该容器的特定走焦算法决定。若算法判定下一个焦点应落在某个容器组件上,系统将采用中心点距离优先的算法来进一步确定容器内的目标子节点。

  4. 走焦算法:每个可获焦的容器组件都有其特定的走焦算法,用于定义焦点转移的规则。
  5. 子组件优先:当子组件处理按键走焦事件,父组件将不再介入。
  • requestFocus

    详见requestFocus,可以主动将焦点转移到指定组件上。

    不可跨窗口,不可跨ArkUI实例申请焦点,可以跨层级页面申请焦点。

  • clearFocus

    详见clearFocus,会清除当前层级页面中的焦点,最终焦点停留在根容器上。

  • focusOnTouch

    详见focusOnTouch,使绑定组件具备点击后获得焦点的能力。若组件本身不可获焦,则此功能无效。若绑定的是容器组件,点击后优先将焦点转移给上一次获焦的子组件,否则转移给第一个可获焦的子组件。

被动走焦

被动走焦是指组件焦点因系统或其他操作而自动转移,无需开发者直接干预,这是焦点系统的默认行为。

目前会被动走焦的机制有:

  • 组件删除:当处于焦点状态的组件被删除时,焦点框架首先尝试将焦点转移到相邻的兄弟组件上,遵循先向后再向前的顺序。若所有兄弟组件均不可获焦,则焦点将释放,并通知其父组件进行焦点处理。
  • 属性变更:若将处于焦点状态的组件的focusable或enabled属性设置为false,或者将visibility属性设置为不可见,系统将自动转移焦点至其他可获焦组件,转移方式与1中相同
  • 层级页面切换:当发生层级页面切换时,如从一个页面跳转到另一个页面,当前页面的焦点将自动释放,新页面可能会根据预设逻辑自动获得焦点。
  • Web组件初始化:对于Web组件,当其被创建时,若其设计需要立即获得焦点(如某些弹出框或输入框),则可能触发焦点转移至该Web组件,其行为属于组件自身的行为逻辑,不属于焦点框架的规格范围。

走焦算法

在焦点管理系统中,每个可获焦的容器都配备有特定的走焦算法,这些算法定义了当使用TAB键、Shift+TAB键或方向键时,焦点如何从当前获焦的子组件转移到下一个可获焦的子组件。

容器采用何种走焦算法取决于其UX(用户体验)规格,并由容器组件进行适配。目前,焦点框架支持三种走焦算法:线性走焦、投影走焦和自定义走焦。

线性走焦算法

线性走焦算法是默认的走焦策略,它基于容器中子节点在节点树中的挂载顺序进行走焦,常用于单方向布局的容器,如Row、Column和Flex容器。运行规则如下:

  • 顺序依赖:走焦顺序完全基于子节点在节点树中的挂载顺序,与它们在界面上的实际布局位置无关。
  • TAB键走焦:使用TAB键时,焦点将按照子节点的挂载顺序依次遍历。
  • 方向键走焦:当使用与容器定义方向垂直的方向键时,容器不接受该方向的走焦请求。例如,在横向的Row容器中,无法使用方向键进行上下移动。
  • 边界处理:当焦点位于容器的首尾子节点时,容器将拒绝与当前焦点方向相反的方向键走焦请求。例如,焦点在一个横向的Row容器的第一个子节点上时,该容器无法处理方向键左的走焦请求。

投影走焦算法

投影走焦算法基于当前获焦组件在走焦方向上的投影,结合子组件与投影的重叠面积和中心点距离进行胜出判定。该算法特别适用于子组件大小不一的容器,目前仅有配置了wrap属性的Flex组件。运行规则如下:

  • 方向键走焦时,判断投影与子组件区域的重叠面积,在所有面积不为0的子组件中,计算它们与当前获焦组件的中心点直线距离,距离最短的胜出,若存在多个备选,则节点树上更靠前的胜出。若无任何子组件与投影由重叠,说明该容器已经无法处理该方向键的走焦请求。
  • TAB键走焦时,先使用规格1,按照方向键右进行判定,若找到则成功退出,若无法找到,则将当前获焦子组件的位置模拟往下移动该获焦子组件的高度,然后再按照方向键左进行投影判定,有投影重叠且中心点直线距离最远的子组件胜出,若无投影重叠的子组件,则表示该容器无法处理本次TAB键走焦请求。
  • Shift+TAB键走焦时,先使用规格1,按照方向键左进行判定,找到则成功退出。若无法找到,则将当前获焦子组件的位置模拟向上移动该获焦子组件的高度,然后再按照方向键右进行投影判定,有投影重叠且中心点直线距离最远的子组件胜出,若无投影重叠的子组件,则表示该容器无法处理本次的Shift+TAB键走焦请求。

自定义走焦算法

由组件自定义的走焦算法,规格由组件定义。

获焦/失焦事件

onFocus(event: () => void)

获焦事件回调,绑定该接口的组件获焦时,回调响应。

onBlur(event:() => void)

失焦事件回调,绑定该接口的组件失焦时,回调响应。

onFocus和onBlur两个接口通常成对使用,来监听组件的焦点变化。

// xxx.ets
@Entry
@Component
struct FocusEventExample {
  @State oneButtonColor: Color = Color.Gray;
  @State twoButtonColor: Color = Color.Gray;
  @State threeButtonColor: Color = Color.Gray;

  build() {
    Column({ space: 20 }) {
      // 通过外接键盘的上下键可以让焦点在三个按钮间移动,按钮获焦时颜色变化,失焦时变回原背景色
      Button('First Button')
        .width(260)
        .height(70)
        .backgroundColor(this.oneButtonColor)
        .fontColor(Color.Black)
          // 监听第一个组件的获焦事件,获焦后改变颜色
        .onFocus(() => {
          this.oneButtonColor = Color.Green;
        })
          // 监听第一个组件的失焦事件,失焦后改变颜色
        .onBlur(() => {
          this.oneButtonColor = Color.Gray;
        })

      Button('Second Button')
        .width(260)
        .height(70)
        .backgroundColor(this.twoButtonColor)
        .fontColor(Color.Black)
          // 监听第二个组件的获焦事件,获焦后改变颜色
        .onFocus(() => {
          this.twoButtonColor = Color.Green;
        })
          // 监听第二个组件的失焦事件,失焦后改变颜色
        .onBlur(() => {
          this.twoButtonColor = Color.Grey;
        })

      Button('Third Button')
        .width(260)
        .height(70)
        .backgroundColor(this.threeButtonColor)
        .fontColor(Color.Black)
          // 监听第三个组件的获焦事件,获焦后改变颜色
        .onFocus(() => {
          this.threeButtonColor = Color.Green;
        })
          // 监听第三个组件的失焦事件,失焦后改变颜色
        .onBlur(() => {
          this.threeButtonColor = Color.Gray ;
        })
    }.width('100%').margin({ top: 20 })
  }
}

上述示例包含以下3步:

  • 应用打开,按下TAB键激活走焦,“First Button”显示焦点态样式:组件外围有一个蓝色的闭合框,onFocus回调响应,背景色变成绿色。
  • 按下TAB键,触发走焦,“Second Button”获焦,onFocus回调响应,背景色变成绿色;“First Button”失焦、onBlur回调响应,背景色变回灰色。
  • 按下TAB键,触发走焦,“Third Button”获焦,onFocus回调响应,背景色变成绿色;“Second Button”失焦、onBlur回调响应,背景色变回灰色。

设置组件是否可获焦

focusable(value: boolean)

设置组件是否可获焦。

按照组件的获焦能力可大致分为三类:

  • 默认可获焦的组件,通常是有交互行为的组件,例如Button、Checkbox,TextInput组件,此类组件无需设置任何属性,默认即可获焦。

  • 有获焦能力,但默认不可获焦的组件,典型的是Text、Image组件,此类组件缺省情况下无法获焦,若需要使其获焦,可使用通用属性focusable(true)使能。对于没有配置focusable属性,有获焦能力但默认不可获焦的组件,为其配置onClick或是单指单击的Tap手势,该组件会隐式地成为可获焦组件。如果其focusable属性被设置为false,即使配置了上述事件,该组件依然不可获焦。

  • 无获焦能力的组件,通常是无任何交互行为的展示类组件,例如Blank、Circle组件,此类组件即使使用focusable属性也无法使其可获焦。

enabled(value: boolean)

设置组件是否可交互。若设置为false,则组件无交互能力,自然无法获焦。

focusOnTouch(value: boolean)

设置当前组件是否支持点击获焦能力。

说明

  • 当某组件处于获焦状态时,将其的focusable属性或enabled属性设置为false,会自动使该组件失焦,然后焦点按照走焦规范将焦点转移给其他组件。
// xxx.ets
@Entry
@Component
struct FocusableExample {
  @State textFocusable: boolean = true;
  @State textEnabled: boolean = true;
  @State color1: Color = Color.Yellow;
  @State color2: Color = Color.Yellow;
  @State color3: Color = Color.Yellow;

  build() {
    Column({ space: 5 }) {
      Text('Default Text')    // 第一个Text组件未设置focusable属性,默认不可获焦
        .borderColor(this.color1)
        .borderWidth(2)
        .width(300)
        .height(70)
        .onFocus(() => {
          this.color1 = Color.Blue;
        })
        .onBlur(() => {
          this.color1 = Color.Yellow;
        })
      Divider()

      Text('focusable: ' + this.textFocusable)    // 第二个Text设置了focusable初始为true,focusableOnTouch为true
        .borderColor(this.color2)
        .borderWidth(2)
        .width(300)
        .height(70)
        .focusable(this.textFocusable)
        .focusOnTouch(true)
        .onFocus(() => {
          this.color2 = Color.Blue;
        })
        .onBlur(() => {
          this.color2 = Color.Yellow;
        })

      Text('enabled: ' + this.textEnabled)    // 第三个Text设置了focusable为true,enabled初始为true
        .borderColor(this.color3)
        .borderWidth(2)
        .width(300)
        .height(70)
        .focusable(true)
        .enabled(this.textEnabled)
        .focusOnTouch(true)
        .onFocus(() => {
          this.color3 = Color.Blue;
        })
        .onBlur(() => {
          this.color3 = Color.Yellow;
        })

      Divider()

      Row() {
        Button('Button1')
          .width(140).height(70)
        Button('Button2')
          .width(160).height(70)
      }

      Divider()
      Button('Button3')
        .width(300).height(70)

      Divider()
    }.width('100%').justifyContent(FlexAlign.Center)
    .onKeyEvent((e) => {
      // 绑定onKeyEvent,在该Column组件获焦时,按下'F'键,可将第二个Text的focusable置反
      if (e.keyCode === 2022 && e.type === KeyType.Down) {
        this.textFocusable = !this.textFocusable;
      }
      // 绑定onKeyEvent,在该Column组件获焦时,按下'G'键,可将第三个Text的enabled置反
      if (e.keyCode === 2023 && e.type === KeyType.Down) {
        this.textEnabled = !this.textEnabled;
      }
    })
  }
}

运行效果:

上述示例包含以下3步:

  • 第一个Text组件没有设置focusable(true)属性,该Text组件无法获焦。
  • 点击第二个Text组件,由于设置了focusOnTouch(true),第二个组件获焦。按下TAB键,触发走焦,仍然是第二个Text组件获焦。按键盘F键,触发onKeyEvent,focusable置为false,第二个Text组件变成不可获焦,焦点自动转移,会自动从Text组件寻找下一个可获焦组件,焦点转移到第三个Text组件上。
  • 按键盘G键,触发onKeyEvent,enabled置为false,第三个Text组件变成不可获焦,焦点自动转移,使焦点转移到Row容器上,容器中使用的是默认配置,会转移到Button1上。

默认焦点

页面的默认焦点

defaultFocus(value: boolean)

设置当前组件是否为当前页面上的默认焦点。

// xxx.ets
@Entry
@Component
struct morenjiaodian {
  @State oneButtonColor: Color = Color.Gray;
  @State twoButtonColor: Color = Color.Gray;
  @State threeButtonColor: Color = Color.Gray;

  build() {
    Column({ space: 20 }) {
      // 通过外接键盘的上下键可以让焦点在三个按钮间移动,按钮获焦时颜色变化,失焦时变回原背景色
      Button('First Button')
        .width(260)
        .height(70)
        .backgroundColor(this.oneButtonColor)
        .fontColor(Color.Black)
          // 监听第一个组件的获焦事件,获焦后改变颜色
        .onFocus(() => {
          this.oneButtonColor = Color.Green;
        })
          // 监听第一个组件的失焦事件,失焦后改变颜色
        .onBlur(() => {
          this.oneButtonColor = Color.Gray;
        })

      Button('Second Button')
        .width(260)
        .height(70)
        .backgroundColor(this.twoButtonColor)
        .fontColor(Color.Black)
          // 监听第二个组件的获焦事件,获焦后改变颜色
        .onFocus(() => {
          this.twoButtonColor = Color.Green;
        })
          // 监听第二个组件的失焦事件,失焦后改变颜色
        .onBlur(() => {
          this.twoButtonColor = Color.Grey;
        })

      Button('Third Button')
        .width(260)
        .height(70)
        .backgroundColor(this.threeButtonColor)
        .fontColor(Color.Black)
          // 设置默认焦点
        .defaultFocus(true)
          // 监听第三个组件的获焦事件,获焦后改变颜色
        .onFocus(() => {
          this.threeButtonColor = Color.Green;
        })
          // 监听第三个组件的失焦事件,失焦后改变颜色
        .onBlur(() => {
          this.threeButtonColor = Color.Gray ;
        })
    }.width('100%').margin({ top: 20 })
  }
}

上述示例包含以下2步:

  • 在第三个Button组件上设置了defaultFocus(true),进入页面后第三个Button默认获焦,显示为绿色
  • 按下TAB键,触发走焦,第三个Button正处于获焦状态,会出现焦点框

容器的默认焦点

容器的默认焦点受到获焦优先级的影响。

defaultFcous与FocusPriority的区别

defaultFocus是用于指定页面首次展示时的默认获焦节点,FocusPriority是用于指定某个容器首次获焦时其子节点的获焦优先级。上述两个属性在某些场景同时配置时行为未定义,例如下面的场景,页面首次展示无法同时满足defaultFocus获焦和高优先级组件获焦。

示例

@Entry
@Component
struct Index {
  build() {
    Row() {
      Button('Button1')
        .defaultFocus(true)
      Button('Button2')
        .focusScopePriority('RowScope', FocusPriority.PREVIOUS)
    }.focusScopeId('RowScope')
  }
}

页面/容器整体获焦时的焦点链

整体获焦与非整体获焦

  • 整体获焦是页面/容器自身作为焦点链的叶节点获焦,获焦后再把焦点链叶节点转移到子孙组件。例如,页面切换、Navigation组件中的路由切换、焦点组走焦、容器组件主动调用requestFocusById等。

  • 非整体获焦是某个组件作为焦点链叶节点获焦,导致其祖先节点跟着获焦。例如TextInput组件主动获取焦点、Tab键在非焦点组场景下走焦等。

整体获焦的焦点链形成

1.页面首次获焦

  • 焦点链叶节点为配置了defaultFocus的节点。

  • 未配置defaultFocus时,焦点停留在页面的根容器上。

2.页面非首次获焦:由上次获焦的节点获焦。

3.获焦链上存在配置了获焦优先级的组件和容器

  • 容器内存在优先级大于PREVIOUS的组件,由优先级最高的组件获焦。

  • 容器内不存在优先级大于PREVIOUS的组件,由上次获焦的节点获焦。例如,窗口失焦后重新获焦。

焦点样式

focusBox(style: FocusBoxStyle)

设置当前组件系统焦点框样式。

import { ColorMetrics, LengthMetrics } from '@kit.ArkUI'

@Entry
@Component
struct RequestFocusExample {
  build() {
    Column({ space: 30 }) {
      Button("small black focus box")
        .focusBox({
          margin: new LengthMetrics(0),
          strokeColor: ColorMetrics.rgba(0, 0, 0),
        })
      Button("large red focus box")
        .focusBox({
          margin: LengthMetrics.px(20),
          strokeColor: ColorMetrics.rgba(255, 0, 0),
          strokeWidth: LengthMetrics.px(10)
        })
    }
    .alignItems(HorizontalAlign.Center)
    .width('100%')
  }
}

上述示例包含以下2步:

  • 进入页面,按下TAB触发走焦,第一个Button获焦,焦点框样式为紧贴边缘的蓝色细框
  • 按下TAB键,走焦到第二个Button,焦点框样式为远离边缘的红色粗框

主动获焦/失焦

  • 使用focusControl中的方法

    requestFocus(value: string): boolean

    调用此接口可以主动让焦点转移至参数指定的组件上,焦点转移生效时间为下一个帧信号。

  • 使用FocusController中的方法

    需先使用UIContext中的getFocusController()方法获取实例,再通过此实例调用对应方法。

    requestFocus(key: string): void

    通过组件的id将焦点转移到组件树对应的实体节点,生效时间为当帧生效。

    clearFocus(): void

    清除焦点,将焦点强制转移到页面根容器节点,焦点链路上其他节点失焦。

// focusTest.ets
@Entry
@Component
struct RequestExample {
  @State btColor: Color = Color.Blue
  @State btColor2: Color = Color.Blue

  build() {
    Column({ space: 20 }) {
      Column({ space: 5 }) {
        Button('Button')
          .width(200)
          .height(70)
          .fontColor(Color.White)
          .focusOnTouch(true)
          .backgroundColor(this.btColor)
          .onFocus(() => {
            this.btColor = Color.Red
          })
          .onBlur(() => {
            this.btColor = Color.Blue
          })
          .id("testButton")

        Button('Button')
          .width(200)
          .height(70)
          .fontColor(Color.White)
          .focusOnTouch(true)
          .backgroundColor(this.btColor2)
          .onFocus(() => {
            this.btColor2 = Color.Red
          })
          .onBlur(() => {
            this.btColor2 = Color.Blue
          })
          .id("testButton2")

        Divider()
          .vertical(false)
          .width("80%")
          .backgroundColor(Color.Black)
          .height(10)

        Button('FocusController.requestFocus')
          .width(200).height(70).fontColor(Color.White)
          .onClick(() => {
            this.getUIContext().getFocusController().requestFocus("testButton")
          })

        Button("focusControl.requestFocus")
          .width(200).height(70).fontColor(Color.White)
          .onClick(() => {
            focusControl.requestFocus("testButton2")
          })

        Button("clearFocus")
          .width(200).height(70).fontColor(Color.White)
          .onClick(() => {
            this.getUIContext().getFocusController().clearFocus()
          })
      }
    }
    .width('100%')
    .height('100%')
  }
}

上述示例包含以下3步:

  • 点击FocusController.requestFocus按钮,第一个Button获焦
  • 点击focusControl.requestFocus按钮,第二个Button获焦
  • 点击clearFocus按钮,第二个Button失焦

焦点组与获焦优先级

focusScopePriority(scopeId: string, priority?: FocusPriority)

设置当前组件在指定容器内获焦的优先级。需要配合focusScopeId一起使用。

focusScopeId(id: string, isGroup?: boolean)

设置当前容器组件的id标识,设置当前容器组件是否为焦点组。

// focusTest.ets
@Entry
@Component
struct FocusableExample {
  @State inputValue: string = ''

  build() {
    Scroll() {
      Row({ space: 20 }) {
        Column({ space: 20 }) {  // 标记为Column1
          Column({ space: 5 }) {
            Button('Group1')
              .width(165)
              .height(40)
              .fontColor(Color.White)
            Row({ space: 5 }) {
              Button()
                .width(80)
                .height(40)
                .fontColor(Color.White)
              Button()
                .width(80)
                .height(40)
                .fontColor(Color.White)
            }
            Row({ space: 5 }) {
              Button()
                .width(80)
                .height(40)
                .fontColor(Color.White)
              Button()
                .width(80)
                .height(40)
                .fontColor(Color.White)
            }
          }.borderWidth(2).borderColor(Color.Red).borderStyle(BorderStyle.Dashed)
          Column({ space: 5 }) {
            Button('Group2')
              .width(165)
              .height(40)
              .fontColor(Color.White)
            Row({ space: 5 }) {
              Button()
                .width(80)
                .height(40)
                .fontColor(Color.White)
              Button()
                .width(80)
                .height(40)
                .fontColor(Color.White)
                .focusScopePriority('ColumnScope1', FocusPriority.PRIOR)  // Column1首次获焦时获焦
            }
            Row({ space: 5 }) {
              Button()
                .width(80)
                .height(40)
                .fontColor(Color.White)
              Button()
                .width(80)
                .height(40)
                .fontColor(Color.White)
            }
          }.borderWidth(2).borderColor(Color.Green).borderStyle(BorderStyle.Dashed)
        }
        .focusScopeId('ColumnScope1')
        Column({ space: 5 }) {  // 标记为Column2
          TextInput({placeholder: 'input', text: this.inputValue})
            .onChange((value: string) => {
              this.inputValue = value
            })
            .width(156)
          Button('Group3')
            .width(165)
            .height(40)
            .fontColor(Color.White)
          Row({ space: 5 }) {
            Button()
              .width(80)
              .height(40)
              .fontColor(Color.White)
            Button()
              .width(80)
              .height(40)
              .fontColor(Color.White)
          }
          Button()
            .width(165)
            .height(40)
            .fontColor(Color.White)
            .focusScopePriority('ColumnScope2', FocusPriority.PREVIOUS)  // Column2获焦时获焦
          Row({ space: 5 }) {
            Button()
              .width(80)
              .height(40)
              .fontColor(Color.White)
            Button()
              .width(80)
              .height(40)
              .fontColor(Color.White)
          }
          Button()
            .width(165)
            .height(40)
            .fontColor(Color.White)
          Row({ space: 5 }) {
            Button()
              .width(80)
              .height(40)
              .fontColor(Color.White)
            Button()
              .width(80)
              .height(40)
              .fontColor(Color.White)
          }
        }.borderWidth(2).borderColor(Color.Orange).borderStyle(BorderStyle.Dashed)
        .focusScopeId('ColumnScope2', true)  // Column2为焦点组
      }.alignItems(VerticalAlign.Top)
    }
  }
}

上述示例包含以下2步:

  • input方框内设置了焦点组,因此按下TAB键后焦点会快速从input中走出去,而按下方向键后可以在input内走焦
  • 左上角的Column没有设置焦点组,因此只能通过Tab键一个一个地走焦

组件获焦能力说明

表1 基础组件获焦能力

基础组件是否有获焦能力focusable默认值
AlphabetIndexertrue
Blankfalse
Buttontrue
CalendarPickertrue
Checkboxtrue
CheckboxGrouptrue
ContainerSpanfalse
DataPanelfalse
DatePickertrue
Dividerfalse
Gaugefalse
Imagefalse
ImageAnimatorfalse
ImageSpanfalse
LoadingProgresstrue
Marqueefalse
Menutrue
MenuItemtrue
MenuItemGroupfalse
Navigationtrue
NavRouterfalse
NavDestinationtrue
PatternLocktrue
Progresstrue
QRCodetrue
Radiotrue
Ratingtrue
RichEditortrue
RichTextfalse
ScrollBarfalse
Searchtrue
Selecttrue
Slidertrue
Spanfalse
Steppertrue
StepperItemtrue
SymbolSpanfalse
SymbolGlyphfalse
Textfalse
TextAreafalse
TextClockfalse
TextInputtrue
TextPickertrue
TextTimerfalse
TimePickerfalse
Toggletrue
XComponentfalse

表2 容器组件获焦能力

容器组件是否可获焦focusable默认值
Badgefalse
Columntrue
ColumnSplittrue
Counterfalse
EmbeddedComponentfalse
Flextrue
FlowItemtrue
FolderStacktrue
FormLinkfalse
GridColtrue
GridRowtrue
Gridtrue
GridItemtrue
Hyperlinktrue
Listtrue
ListItemtrue
ListItemGrouptrue
Navigatortrue
Refreshtrue
RelativeContainerfalse
Rowtrue
RowSplittrue
Scrolltrue
SideBarContainertrue
Stacktrue
Swipertrue
Tabstrue
TabContenttrue
WaterFlowfalse
WithThemetrue

表3 媒体组件获焦能力

媒体组件是否可获焦focusable默认值
Videotrue

最后

有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。 

点击→【纯血版鸿蒙全套最新学习资料】希望这一份鸿蒙学习资料能够给大家带来帮助!~


 鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

点击→纯血版全套鸿蒙HarmonyOS学习资料

2.视频学习资料+学习PDF文档

这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、(南向驱动、嵌入式等)鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。

HarmonyOS Next 最新全套视频教程

​​

 

 大厂面试必问面试题

​​

鸿蒙南向开发技术

​​

鸿蒙APP开发必备

​​

《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

​​

《鸿蒙开发基础》

​​

《鸿蒙开发进阶》

《鸿蒙进阶实战》

​​


点击→纯血版全套鸿蒙HarmonyOS学习资料

总结

总的来说,华为鸿蒙不再兼容安卓,对程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,才能在这个变革的时代中立于不败之地。 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker安装teslamate

要求 Docker(如果不熟悉 Docker,请参阅安装 Docker 和 Docker Compose)一台始终开启的机器,因此 TeslaMate 可以持续获取数据计算机上至少有 1 GB 的 RAM 才能成功安装。外部互联网访问,与 tesla.com 交谈 创建一个名…

【数据结构】队列篇

文章目录 1.队列1.1 队列的概念及结构 2. 队列的实现2.1 准备工作2.2 队列的初始化2.3 队尾入队列2.4 队头出队列2.5 获取队列头部元素2.6 获取队列队尾元素2.7 获取队列有效元素个数2.8 检测队列是否为空2.9 销毁队列 3. 代码整合 1.队列 1.1 队列的概念及结构 队列&#xff…

黑马Java零基础视频教程精华部分_15_基本查找/顺序查找、二分查找/折半查找、插值查找、斐波那契查找、分块查找、哈希查找

系列文章目录 文章目录 系列文章目录一、基本查找/顺序查找核心思想:从0索引开始挨个往后查找代码:练习:定义一个方法利用基本查找,查询某个元素在数组中的索引,数组包含重复数据。 二、二分查找/折半查找核心思想:属于…

LVS多模式集群攻略!

NAT模式下的lvs集群 lvs-nat概念:修改请求报文的目标IP,多目标IP的DNAT,本质是多目标IP的DNAT,通过将请求报文中的目标地址和目标端口修改为某挑出的RS的RIP和PORT实现转发 最终实现效果: 1.Director 服务器采用双网卡&#xff…

Qt入门(二):第一个Qt项目

新建项目 打开Qt Creator,新建项目,然后一路next 到这一步baseclass有三种选择: QMainWindow:主窗口基类,相较于QWidget,多了菜单栏等杂七杂八的东西。QWidget:最基础的窗口基类&#xff0…

编译运行 Byconity

我的系统是centos,因此用他们的docker编译并用他们的docker-compose运行,以下流程亲测可跑: 拉取并编译 https://github.com/ByConity/ByConity/tree/master/docker/debian/dev-env 运行 https://github.com/ByConity/ByConity/blob/master/d…

Matplotlib | 一文搞定Matplotlib从入门到实战演练!

文章目录 1 什么是Matplotlib1.1 Matplotlib的安装1.2 Matplotlib的基本使用 2 绘制直线3 绘制折线设置标签文字和线条粗细设置中文标题风格的设置 4 绘制曲线绘制曲线yx^2绘制正弦曲线和余弦曲线画布分区 5 绘制散点图绘制不同种类不同颜色的线 6 绘制条形图(柱状&…

代码之外的生存指南——生产力

自己感觉今天都没有喝一口水的时间忙忙碌碌的工作了一天,但是到快下班或晚上回想一下今天自己到底在忙些什么的时候,却好像也没有做些什么对自己工作主线有意义的事情,大多时候时间都花费在了那些检查邮件、泡茶、拨打电话、开会议等干扰内容…

科普文:业务场景之常见10家HIS厂商概叙

智慧医院信息化经过几十年的发展,涌现出了一大批优秀的建设企业,我们选取了市面上部分主流的HIS厂商进行了汇集,包括厂商的发展情况、产品情况、技术情况、案例情况等。 卫宁健康 WINEX 口号:软件认知医疗 最新产品:…

集智书童 | 浙江大学 蚂蚁集团提出 PAI,一种无需训练减少 LVLM 幻觉的方法 !

本文来源公众号“集智书童”,仅用于学术分享,侵权删,干货满满。 原文链接:浙江大学 & 蚂蚁集团提出 PAI,一种无需训练减少 LVLM 幻觉的方法 ! 浙江大学 & 蚂蚁集团提出 PAI,一种无需训…

BERT预训练

一、动机 1、在NLP中的迁移学习中,使用预训练好的模型抽取词、句子的特征,不更新预训练好的模型,而是在需要构建新的网络来抓取新任务需要的信息,也就是最后面加上一个MLP做分类; 2、由于基于微调的NLP模型&#xff…

21. 合并两个有序链表(递归)

目录 一;题目: 二代码; 三:结果: 一;题目: 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 二代码; /*** Definition for singly-linked list.* struct ListNode {* …

HTMX 和 FastAPI 绝佳搭配

FastAPI的优势 FastAPI 是一个现代、快速(高性能)的 Web 框架,用于基于标准 Python 类型提示使用 Python 3.7 构建 API。以下是它的一些主要优点: 性能:FastAPI 基于 Starlette 和 Pydantic 构建,使其与 …

Linux 中 core dump 异常的分析

目录 一、概述二、发生 core dump 的原因1. 空指针或非法指针2. 数组越界或指针越界3. 数据竞争 三、分析 core dump 的方法1. 启用 core dump2. 触发 core dump2.1 因空指针解引用而崩溃2.2 通过 信号触发 core dump 3. 利用 gdb 分析 core dump 一、概述 在 UNIX 系统中&…

sqli-labs第一关详细解答

首先判断是否有注入点 发现and 11 和 and 12结果一样,所以应该是字符型注入,需要对单引号做闭合 做闭合后发现报错,提示Limit 0,1,那就说明存在注入点,但是要注释掉后面的limit 0,1 使用--注释掉limit 0,1后&#xff…

25考研英语长难句Day05

25考研英语长难句Day05 【词组】【断句】 【词组】 单词解释gelimpsen.一瞥、瞥见rapidly adv.迅速;迅速地;高速;急速地;急促scene n.场景;(尤指不愉快事件发生的)地点,现场;场面&a…

记录下泡面神器的满血复活-Kindle Voyage刷安卓系统记录

Kindle在国内已经没有服务了,一段时间内通过连手机热点(上下班通勤),用内置浏览器访问微信读书,但体验不是很好,在考虑是否购买一个国内的墨水屏阅读器时,偶然想到了是否可以刷安卓,然后装微信读书的墨水屏…

超详细!网络安全知识入门及学习流程

第一章:网络安全的基本概念和术 一、网络安全的基本概念 1.保密性(Confidentiality) 定义:确保信息在存储、传输和处理过程中不被未授权的人员访问或获取。例子:企业的商业机密文件被加密存储,只有拥有正…

5个理由让你爱上CleanMyMac2025告别卡顿,迎接极速体验!

CleanMyMac是一款Mac电脑专用的清理工具,具有系统垃圾、大型旧文件、邮件附件、iTunes垃圾、软件卸载残余等清理功能。 它采用先进的扫描技术,快速识别并清除垃圾文件,释放磁盘空间,提高系统运行速度。 同时,它还具备…

Android经典实战之Kotlin中实现圆角图片和圆形图片

本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点 实现圆角是一个很常见的需求,也有很多种方式,这里介绍2种,实现起来都不麻烦,很方便 方法一&…