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

基础概念

焦点、焦点链和走焦

  • 焦点:指向当前应用界面上唯一的一个可交互元素,当用户使用键盘、电视遥控器、车机摇杆/旋钮等非指向性输入设备与应用程序进行间接交互时,基于焦点的导航和交互是重要的输入手段。
  • 焦点链:在应用的组件树形结构中,当一个组件获得焦点时,从根节点到该组件节点的整条路径上的所有节点都会被视为处于焦点状态,形成一条连续的焦点链。
  • 走焦:指焦点在应用内的组件之间转移的行为。这一过程对用户是透明的,但开发者可以通过监听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')
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值