1# 焦点事件
2
3## 基础概念与规范
4
5### 基础概念
6
7**焦点、焦点链和走焦**
8
9- 焦点:指向当前应用界面上唯一的一个可交互元素,当用户使用键盘、电视遥控器、车机摇杆/旋钮等非指向性输入设备与应用程序进行间接交互时,基于焦点的导航和交互是重要的输入手段。
10- 焦点链:在应用的组件树形结构中,当一个组件获得焦点时,从根节点到该组件节点的整条路径上的所有节点都会被视为处于焦点状态,形成一条连续的焦点链。
11- 走焦:指焦点在应用内的组件之间转移的行为。这一过程对用户是透明的,但开发者可以通过监听onFocus(焦点获取)和onBlur(焦点失去)事件来捕捉这些变化。关于走焦的具体方式和规则,详见[走焦规范](#走焦规范)。
12
13
14**焦点态**
15
16用来指向当前获焦组件的样式。
17
18- 显示规则:默认情况下焦点态不会显示,只有当应用进入激活态后,焦点态才会显示。因此,虽然获得焦点的组件不一定显示焦点态(取决于是否处于激活态),但显示焦点态的组件必然是获得焦点的。大部分组件内置了焦点态样式,开发者同样可以使用样式接口进行自定义,一旦自定义,组件将不再显示内置的焦点态样式。在焦点链中,若多个组件同时拥有焦点态,系统将采用子组件优先的策略,优先显示子组件的焦点态,并且仅显示一个焦点态。
19- 进入激活态:使用外接键盘按下TAB键/使用FocusController的activate(true)方法才会进入焦点的激活态,进入激活态后,才可以使用键盘TAB键/方向键进行走焦。首次用来激活焦点态的TAB键不会触发走焦。
20- 退出激活态:当应用收到FocusController的active(false)方法/点击事件时(包括手指触屏的按下事件和鼠标左键的按下事件),焦点的激活态会退出。
21
22
23**层级页面**
24
25层级页面是焦点框架中特定容器组件的统称,涵盖Page、Dialog、SheetPage、ModalPage、Menu、Popup、NavBar、NavDestination等。这些组件通常具有以下关键特性:
26
27- 视觉层级独立性:从视觉呈现上看,这些组件独立于其他页面内容,并通常位于其上方,形成视觉上的层级差异。
28- 焦点跟随:此类组件在首次创建并展示之后,会立即将应用内焦点抢占。
29- 走焦范围限制:当焦点位于这些组件内部时,用户无法通过键盘按键将焦点转移到组件外部的其他元素上,焦点移动仅限于组件内部。
30
31在一个应用程序中,任何时候都至少存在一个层级页面组件,并且该组件会持有当前焦点。当该层级页面关闭或不再可见时,焦点会自动转移到下一个可用的层级页面组件上,确保用户交互的连贯性和一致性。
32
33> **说明:**
34>
35> Popup组件在focusable属性(组件属性,非通用属性)为false的时候,不会有第2条特性。
36>
37> NavBar、NavDestination没有第3条特性,对于它们的走焦范围,是与它们的首个父层级页面相同的。
38
39**根容器**
40
41根容器是层级页面内的概念,当某个层级页面首次创建并展示时,根据层级页面的特性,焦点会立即被该页面抢占。此时,该层级页面所在焦点链的末端节点将成为默认焦点,而这个默认焦点通常位于该层级页面的根容器上。
42
43在缺省状态下,层级页面的默认焦点位于其根容器上,但开发者可以通过defaultFocus属性来自定义这一行为。
44
45当焦点位于根容器时,首次按下TAB键不仅会使焦点进入激活状态,还会触发焦点向子组件的传递。如果子组件本身也是一个容器,则焦点会继续向下传递,直至到达叶子节点。传递规则是:优先传递给上一次获得焦点的子节点,如果不存在这样的节点,则默认传递给第一个子节点。
46
47### 走焦规范
48
49根据走焦的触发方式,可以分为主动走焦和被动走焦。
50
51**主动走焦**
52
53
54指开发者/用户主观行为导致的焦点移动,包括:使用外接键盘的按键走焦(TAB键/Shift+TAB键/方向键)、使用requestFocus申请焦点、clearFocus清除焦点、focusOnTouch点击申请焦点等接口导致的焦点转移。
55
56
57- 按键走焦
581. 前提:当前应用需处于焦点激活态。
592. 范围限制:按键走焦仅在当前获得焦点的层级页面内进行,具体参见“层级页面”中的“走焦范围限制”部分。
603. 按键类型:
61TAB键:遵循Z字型遍历逻辑,完成当前范围内所有叶子节点的遍历,到达当前范围内的最后一个组件后,继续按下TAB键,焦点将循环至范围内的第一个可获焦组件,实现循环走焦。
62Shift+TAB键:与TAB键具有相反的焦点转移效果。
63方向键(上、下、左、右):遵循十字型移动策略,在单层容器中,焦点的转移由该容器的特定走焦算法决定。若算法判定下一个焦点应落在某个容器组件上,系统将采用中心点距离优先的算法来进一步确定容器内的目标子节点。
644. 走焦算法:每个可获焦的容器组件都有其特定的走焦算法,用于定义焦点转移的规则。
655. 子组件优先:当子组件处理按键走焦事件,父组件将不再介入。
66
67- requestFocus
68详见[主动获焦失焦](#主动获焦失焦),可以主动将焦点转移到指定组件上。
69不可跨窗口,不可跨ArkUI实例申请焦点,可以跨层级页面申请焦点。
70
71- clearFocus
72详见[clearFocus](../reference/apis-arkui/js-apis-arkui-UIContext.md#clearfocus12),会清除当前层级页面中的焦点,最终焦点停留在根容器上。
73
74- focusOnTouch
75详见[focusOnTouch](../reference/apis-arkui/arkui-ts/ts-universal-attributes-focus.md#focusontouch9),使绑定组件具备点击后获得焦点的能力。若组件本身不可获焦,则此功能无效。若绑定的是容器组件,点击后优先将焦点转移给上一次获焦的子组件,否则转移给第一个可获焦的子组件。
76
77
78**被动走焦**
79
80被动走焦是指组件焦点因系统或其他操作而自动转移,无需开发者直接干预,这是焦点系统的默认行为。
81
82
83目前会被动走焦的机制有:
84
85- 组件删除:当处于焦点状态的组件被删除时,焦点框架首先尝试将焦点转移到相邻的兄弟组件上,遵循先向后再向前的顺序。若所有兄弟组件均不可获焦,则焦点将释放,并通知其父组件进行焦点处理。
86- 属性变更:若将处于焦点状态的组件的focusable或enabled属性设置为false,或者将visibility属性设置为不可见,系统将自动转移焦点至其他可获焦组件,转移方式与1中相同。
87- 层级页面切换:当发生层级页面切换时,如从一个页面跳转到另一个页面,当前页面的焦点将自动释放,新页面可能会根据预设逻辑自动获得焦点。
88- Web组件初始化:对于Web组件,当其被创建时,若其设计需要立即获得焦点(如某些弹出框或输入框),则可能触发焦点转移至该Web组件,其行为属于组件自身的行为逻辑,不属于焦点框架的规格范围。
89
90### 走焦算法
91
92在焦点管理系统中,每个可获焦的容器都配备有特定的走焦算法,这些算法定义了当使用TAB键、Shift+TAB键或方向键时,焦点如何从当前获焦的子组件转移到下一个可获焦的子组件。
93
94容器采用何种走焦算法取决于其UX(用户体验)规格,并由容器组件进行适配。目前,焦点框架支持三种走焦算法:线性走焦、投影走焦和自定义走焦。
95
96**线性走焦算法**
97
98
99线性走焦算法是默认的走焦策略,它基于容器中子节点在节点树中的挂载顺序进行走焦,常用于单方向布局的容器,如Row、Column和Flex容器。运行规则如下:
100
101
102- 顺序依赖:走焦顺序完全基于子节点在节点树中的挂载顺序,与它们在界面上的实际布局位置无关。
103- TAB键走焦:使用TAB键时,焦点将按照子节点的挂载顺序依次遍历。
104- 方向键走焦:当使用与容器定义方向垂直的方向键时,容器不接受该方向的走焦请求。例如,在横向的Row容器中,无法使用方向键进行上下移动。
105- 边界处理:当焦点位于容器的首尾子节点时,容器将拒绝与当前焦点方向相反的方向键走焦请求。例如,焦点在一个横向的Row容器的第一个子节点上时,该容器无法处理方向键左的走焦请求。
106
107
108**投影走焦算法**
109
110投影走焦算法基于当前获焦组件在走焦方向上的投影,结合子组件与投影的重叠面积和中心点距离进行胜出判定。该算法特别适用于子组件大小不一的容器,目前仅有配置了wrap属性的Flex组件。运行规则如下:
111
112
113- 方向键走焦时,判断投影与子组件区域的重叠面积,在所有面积不为0的子组件中,计算它们与当前获焦组件的中心点直线距离,距离最短的胜出,若存在多个备选,则节点树上更靠前的胜出。若无任何子组件与投影由重叠,说明该容器已经无法处理该方向键的走焦请求。
114- TAB键走焦时,先使用规格1,按照方向键右进行判定,若找到则成功退出,若无法找到,则将当前获焦子组件的位置模拟往下移动该获焦子组件的高度,然后再按照方向键左进行投影判定,有投影重叠且中心点直线距离最远的子组件胜出,若无投影重叠的子组件,则表示该容器无法处理本次TAB键走焦请求。
115- Shift+TAB键走焦时,先使用规格1,按照方向键左进行判定,找到则成功退出。若无法找到,则将当前获焦子组件的位置模拟向上移动该获焦子组件的高度,然后再按照方向键右进行投影判定,有投影重叠且中心点直线距离最远的子组件胜出,若无投影重叠的子组件,则表示该容器无法处理本次的Shift+TAB键走焦请求。
116
117
118**自定义走焦算法**
119
120由组件自定义的走焦算法,规格由组件定义。
121
122## 获焦/失焦事件
123
124```ts
125onFocus(event: () => void)
126```
127
128
129获焦事件回调,绑定该接口的组件获焦时,回调响应。
130
131```ts
132onBlur(event:() => void)
133```
134
135失焦事件回调,绑定该接口的组件失焦时,回调响应。
136
137onFocus和onBlur两个接口通常成对使用,来监听组件的焦点变化。
138
139```ts
140// xxx.ets
141@Entry
142@Component
143struct FocusEventExample {
144  @State oneButtonColor: Color = Color.Gray;
145  @State twoButtonColor: Color = Color.Gray;
146  @State threeButtonColor: Color = Color.Gray;
147
148  build() {
149    Column({ space: 20 }) {
150      // 通过外接键盘的上下键可以让焦点在三个按钮间移动,按钮获焦时颜色变化,失焦时变回原背景色
151      Button('First Button')
152        .width(260)
153        .height(70)
154        .backgroundColor(this.oneButtonColor)
155        .fontColor(Color.Black)
156          // 监听第一个组件的获焦事件,获焦后改变颜色
157        .onFocus(() => {
158          this.oneButtonColor = Color.Green;
159        })
160          // 监听第一个组件的失焦事件,失焦后改变颜色
161        .onBlur(() => {
162          this.oneButtonColor = Color.Gray;
163        })
164
165      Button('Second Button')
166        .width(260)
167        .height(70)
168        .backgroundColor(this.twoButtonColor)
169        .fontColor(Color.Black)
170          // 监听第二个组件的获焦事件,获焦后改变颜色
171        .onFocus(() => {
172          this.twoButtonColor = Color.Green;
173        })
174          // 监听第二个组件的失焦事件,失焦后改变颜色
175        .onBlur(() => {
176          this.twoButtonColor = Color.Grey;
177        })
178
179      Button('Third Button')
180        .width(260)
181        .height(70)
182        .backgroundColor(this.threeButtonColor)
183        .fontColor(Color.Black)
184          // 监听第三个组件的获焦事件,获焦后改变颜色
185        .onFocus(() => {
186          this.threeButtonColor = Color.Green;
187        })
188          // 监听第三个组件的失焦事件,失焦后改变颜色
189        .onBlur(() => {
190          this.threeButtonColor = Color.Gray ;
191        })
192    }.width('100%').margin({ top: 20 })
193  }
194}
195```
196
197
198![zh-cn_image_0000001511740584](figures/zh-cn_image_0000001511740584.gif)
199
200
201上述示例包含以下3步:
202
203- 应用打开,按下TAB键激活走焦,“First Button”显示焦点态样式:组件外围有一个蓝色的闭合框,onFocus回调响应,背景色变成绿色。
204- 按下TAB键,触发走焦,“Second Button”获焦,onFocus回调响应,背景色变成绿色;“First Button”失焦,onBlur回调响应,背景色变回灰色。
205- 按下TAB键,触发走焦,“Third Button”获焦,onFocus回调响应,背景色变成绿色;“Second Button”失焦,onBlur回调响应,背景色变回灰色。
206
207## 设置组件是否可获焦
208
209```ts
210focusable(value: boolean)
211```
212
213设置组件是否可获焦。
214
215按照组件的获焦能力可大致分为三类:
216
217- 默认可获焦的组件,通常是有交互行为的组件,例如Button、Checkbox、TextInput组件,此类组件无需设置任何属性,默认即可获焦。
218
219- 有获焦能力,但默认不可获焦的组件,典型的是Text、Image组件,此类组件缺省情况下无法获焦,若需要使其获焦,可使用通用属性focusable(true)使能。对于没有配置focusable属性,有获焦能力但默认不可获焦的组件,为其配置onClick或是单指单击的Tap手势,该组件会隐式地成为可获焦组件。如果其focusable属性被设置为false,即使配置了上述事件,该组件依然不可获焦。
220
221- 无获焦能力的组件,通常是无任何交互行为的展示类组件,例如Blank、Circle组件,此类组件即使使用focusable属性也无法使其可获焦。
222
223
224```ts
225enabled(value: boolean)
226```
227
228设置组件可交互性属性[enabled](../reference/apis-arkui/arkui-ts/ts-universal-attributes-enable.md#enabled)为`false`,则组件不可交互,无法获焦。
229
230
231```ts
232visibility(value: Visibility)
233```
234
235设置组件可见性属性[visibility](../reference/apis-arkui/arkui-ts/ts-universal-attributes-visibility.md#visibility)为`Visibility.None`或`Visibility.Hidden`,则组件不可见,无法获焦。
236
237
238```ts
239focusOnTouch(value: boolean)
240```
241
242设置当前组件是否支持点击获焦能力。
243
244
245>**说明:**
246>
247>当某组件处于获焦状态时,将其的focusable属性或enabled属性设置为false,会自动使该组件失焦,然后焦点按照[走焦规范](#走焦规范)将焦点转移给其他组件。
248
249
250```ts
251// xxx.ets
252@Entry
253@Component
254struct FocusableExample {
255  @State textFocusable: boolean = true;
256  @State textEnabled: boolean = true;
257  @State color1: Color = Color.Yellow;
258  @State color2: Color = Color.Yellow;
259  @State color3: Color = Color.Yellow;
260
261  build() {
262    Column({ space: 5 }) {
263      Text('Default Text')    // 第一个Text组件未设置focusable属性,默认不可获焦
264        .borderColor(this.color1)
265        .borderWidth(2)
266        .width(300)
267        .height(70)
268        .onFocus(() => {
269          this.color1 = Color.Blue;
270        })
271        .onBlur(() => {
272          this.color1 = Color.Yellow;
273        })
274      Divider()
275
276      Text('focusable: ' + this.textFocusable)    // 第二个Text设置了focusable初始为true,focusableOnTouch为true
277        .borderColor(this.color2)
278        .borderWidth(2)
279        .width(300)
280        .height(70)
281        .focusable(this.textFocusable)
282        .focusOnTouch(true)
283        .onFocus(() => {
284          this.color2 = Color.Blue;
285        })
286        .onBlur(() => {
287          this.color2 = Color.Yellow;
288        })
289
290      Text('enabled: ' + this.textEnabled)    // 第三个Text设置了focusable为true,enabled初始为true
291        .borderColor(this.color3)
292        .borderWidth(2)
293        .width(300)
294        .height(70)
295        .focusable(true)
296        .enabled(this.textEnabled)
297        .focusOnTouch(true)
298        .onFocus(() => {
299          this.color3 = Color.Blue;
300        })
301        .onBlur(() => {
302          this.color3 = Color.Yellow;
303        })
304
305      Divider()
306
307      Row() {
308        Button('Button1')
309          .width(140).height(70)
310        Button('Button2')
311          .width(160).height(70)
312      }
313
314      Divider()
315      Button('Button3')
316        .width(300).height(70)
317
318      Divider()
319    }.width('100%').justifyContent(FlexAlign.Center)
320    .onKeyEvent((e) => {
321      // 绑定onKeyEvent,在该Column组件获焦时,按下'F'键,可将第二个Text的focusable置反
322      if (e.keyCode === 2022 && e.type === KeyType.Down) {
323        this.textFocusable = !this.textFocusable;
324      }
325      // 绑定onKeyEvent,在该Column组件获焦时,按下'G'键,可将第三个Text的enabled置反
326      if (e.keyCode === 2023 && e.type === KeyType.Down) {
327        this.textEnabled = !this.textEnabled;
328      }
329    })
330  }
331}
332```
333
334
335运行效果:
336
337
338![focus-1.gif](figures/focus-1.gif)
339
340上述示例包含以下3步:
341
342
343- 第一个Text组件没有设置focusable(true)属性,该Text组件无法获焦。
344- 点击第二个Text组件,由于设置了focusOnTouch(true),第二个组件获焦。按下TAB键,触发走焦,仍然是第二个Text组件获焦。按键盘F键,触发onKeyEvent,focusable置为false,第二个Text组件变成不可获焦,焦点自动转移,会自动从Text组件寻找下一个可获焦组件,焦点转移到第三个Text组件上。
345- 按键盘G键,触发onKeyEvent,enabled置为false,第三个Text组件变成不可获焦,焦点自动转移,使焦点转移到Row容器上,容器中使用的是默认配置,会转移到Button1上。
346
347## 默认焦点
348
349### 页面的默认焦点
350
351```ts
352defaultFocus(value: boolean)
353```
354
355设置当前组件是否为当前页面上的默认焦点。
356
357
358```ts
359// xxx.ets
360@Entry
361@Component
362struct morenjiaodian {
363  @State oneButtonColor: Color = Color.Gray;
364  @State twoButtonColor: Color = Color.Gray;
365  @State threeButtonColor: Color = Color.Gray;
366
367  build() {
368    Column({ space: 20 }) {
369      // 通过外接键盘的上下键可以让焦点在三个按钮间移动,按钮获焦时颜色变化,失焦时变回原背景色
370      Button('First Button')
371        .width(260)
372        .height(70)
373        .backgroundColor(this.oneButtonColor)
374        .fontColor(Color.Black)
375          // 监听第一个组件的获焦事件,获焦后改变颜色
376        .onFocus(() => {
377          this.oneButtonColor = Color.Green;
378        })
379          // 监听第一个组件的失焦事件,失焦后改变颜色
380        .onBlur(() => {
381          this.oneButtonColor = Color.Gray;
382        })
383
384      Button('Second Button')
385        .width(260)
386        .height(70)
387        .backgroundColor(this.twoButtonColor)
388        .fontColor(Color.Black)
389          // 监听第二个组件的获焦事件,获焦后改变颜色
390        .onFocus(() => {
391          this.twoButtonColor = Color.Green;
392        })
393          // 监听第二个组件的失焦事件,失焦后改变颜色
394        .onBlur(() => {
395          this.twoButtonColor = Color.Grey;
396        })
397
398      Button('Third Button')
399        .width(260)
400        .height(70)
401        .backgroundColor(this.threeButtonColor)
402        .fontColor(Color.Black)
403          // 设置默认焦点
404        .defaultFocus(true)
405          // 监听第三个组件的获焦事件,获焦后改变颜色
406        .onFocus(() => {
407          this.threeButtonColor = Color.Green;
408        })
409          // 监听第三个组件的失焦事件,失焦后改变颜色
410        .onBlur(() => {
411          this.threeButtonColor = Color.Gray ;
412        })
413    }.width('100%').margin({ top: 20 })
414  }
415}
416```
417
418![defaultFocus.gif](figures/defaultFocus.gif)
419
420上述示例包含以下2步:
421
422- 在第三个Button组件上设置了defaultFocus(true),进入页面后第三个Button默认获焦,显示为绿色。
423- 按下TAB键,触发走焦,第三个Button正处于获焦状态,会出现焦点框。
424
425### 容器的默认焦点
426
427容器的默认焦点受到[获焦优先级](#焦点组与获焦优先级)的影响。
428
429**defaultFocus与FocusPriority的区别**
430
431[defaultFocus](../reference/apis-arkui/arkui-ts/ts-universal-attributes-focus.md#defaultfocus9)是用于指定页面首次展示时的默认获焦节点,[FocusPriority](../reference/apis-arkui/arkui-ts/ts-universal-attributes-focus.md#focuspriority12)是用于指定某个容器首次获焦时其子节点的获焦优先级。上述两个属性在某些场景同时配置时行为未定义,例如下面的场景,页面首次展示无法同时满足defaultFocus获焦和高优先级组件获焦。
432
433示例
434
435```ts
436@Entry
437@Component
438struct Index {
439  build() {
440    Row() {
441      Button('Button1')
442        .defaultFocus(true)
443      Button('Button2')
444        .focusScopePriority('RowScope', FocusPriority.PREVIOUS)
445    }.focusScopeId('RowScope')
446  }
447}
448```
449
450### 页面/容器整体获焦时的焦点链
451
452**整体获焦与非整体获焦**
453
454- 整体获焦是页面/容器自身作为焦点链的叶节点获焦,获焦后再把焦点链叶节点转移到子孙组件。例如,页面切换、Navigation组件中的路由切换、焦点组走焦、容器组件主动调用requestFocusById等。
455
456- 非整体获焦是某个组件作为焦点链叶节点获焦,导致其祖先节点跟着获焦。例如TextInput组件主动获取焦点、Tab键在非焦点组场景下走焦等。
457
458**整体获焦的焦点链形成**
459
4601.页面首次获焦:
461
462- 焦点链叶节点为配置了defaultFocus的节点。
463
464- 未配置defaultFocus时,焦点停留在页面的根容器上。
465
4662.页面非首次获焦:由上次获焦的节点获焦。
467
4683.获焦链上存在配置了获焦优先级的组件和容器:
469
470- 容器内存在优先级大于PREVIOUS的组件,由优先级最高的组件获焦。
471
472- 容器内不存在优先级大于PREVIOUS的组件,由上次获焦的节点获焦。例如,窗口失焦后重新获焦。
473
474
475## 焦点样式
476
477
478```ts
479focusBox(style: FocusBoxStyle)
480```
481
482设置当前组件系统焦点框样式。
483
484```ts
485import { ColorMetrics, LengthMetrics } from '@kit.ArkUI'
486
487@Entry
488@Component
489struct RequestFocusExample {
490  build() {
491    Column({ space: 30 }) {
492      Button("small black focus box")
493        .focusBox({
494          margin: new LengthMetrics(0),
495          strokeColor: ColorMetrics.rgba(0, 0, 0),
496        })
497      Button("large red focus box")
498        .focusBox({
499          margin: LengthMetrics.px(20),
500          strokeColor: ColorMetrics.rgba(255, 0, 0),
501          strokeWidth: LengthMetrics.px(10)
502        })
503    }
504    .alignItems(HorizontalAlign.Center)
505    .width('100%')
506  }
507}
508```
509
510![focusBox](figures/focusBox.gif)
511
512
513上述示例包含以下2步:
514
515- 进入页面,按下TAB触发走焦,第一个Button获焦,焦点框样式为紧贴边缘的蓝色细框。
516- 按下TAB键,走焦到第二个Button,焦点框样式为远离边缘的红色粗框。
517
518## 主动获焦/失焦
519
520- 使用FocusController中的方法
521
522  更推荐使用FocusController中的requestFocus主动获取焦点。优势如下:
523  - 当前帧生效,避免被下一帧组件树变化影响。
524  - 有异常值返回,便于排查主动获取焦点失败的原因。
525  - 避免多实例场景中取到错误实例。
526
527  需先使用UIContext中的[getFocusController()](../reference/apis-arkui/js-apis-arkui-UIContext.md#getfocuscontroller12)方法获取实例,再通过此实例调用对应方法。
528
529  ```ts
530  requestFocus(key: string): void
531  ```
532  通过组件的id将焦点转移到组件树对应的实体节点,生效时间为当帧生效。
533
534  ```ts
535  clearFocus(): void
536  ```
537  清除焦点,将焦点强制转移到页面根容器节点,焦点链路上其他节点失焦。
538
539- 使用focusControl中的方法
540  ```ts
541  requestFocus(value: string): boolean
542  ```
543
544  调用此接口可以主动让焦点转移至参数指定的组件上,焦点转移生效时间为下一个帧信号。
545
546
547```ts
548// focusTest.ets
549@Entry
550@Component
551struct RequestExample {
552  @State btColor: string = '#ff2787d9'
553  @State btColor2: string = '#ff2787d9'
554
555  build() {
556    Column({ space: 20 }) {
557      Column({ space: 5 }) {
558        Button('Button')
559          .width(200)
560          .height(70)
561          .fontColor(Color.White)
562          .focusOnTouch(true)
563          .backgroundColor(this.btColor)
564          .onFocus(() => {
565            this.btColor = '#ffd5d5d5'
566          })
567          .onBlur(() => {
568            this.btColor = '#ff2787d9'
569          })
570          .id("testButton")
571
572        Button('Button')
573          .width(200)
574          .height(70)
575          .fontColor(Color.White)
576          .focusOnTouch(true)
577          .backgroundColor(this.btColor2)
578          .onFocus(() => {
579            this.btColor2 = '#ffd5d5d5'
580          })
581          .onBlur(() => {
582            this.btColor2 = '#ff2787d9'
583          })
584          .id("testButton2")
585
586        Divider()
587          .vertical(false)
588          .width("80%")
589          .backgroundColor('#ff707070')
590          .height(10)
591
592        Button('FocusController.requestFocus')
593          .width(200).height(70).fontColor(Color.White)
594          .onClick(() => {
595            this.getUIContext().getFocusController().requestFocus("testButton")
596          })
597          .backgroundColor('#ff2787d9')
598
599        Button("focusControl.requestFocus")
600          .width(200).height(70).fontColor(Color.White)
601          .onClick(() => {
602            focusControl.requestFocus("testButton2")
603          })
604          .backgroundColor('#ff2787d9')
605
606        Button("clearFocus")
607          .width(200).height(70).fontColor(Color.White)
608          .onClick(() => {
609            this.getUIContext().getFocusController().clearFocus()
610          })
611          .backgroundColor('#ff2787d9')
612      }
613    }
614    .width('100%')
615    .height('100%')
616  }
617}
618```
619
620![focus-2](figures/focus-2.gif)
621
622上述示例包含以下3步:
623
624- 点击FocusController.requestFocus按钮,第一个Button获焦。
625- 点击focusControl.requestFocus按钮,第二个Button获焦。
626- 点击clearFocus按钮,第二个Button失焦。
627
628## 焦点组与获焦优先级
629
630```ts
631focusScopePriority(scopeId: string, priority?: FocusPriority)
632```
633
634设置当前组件在指定容器内获焦的优先级。需要配合focusScopeId一起使用。
635
636
637```ts
638focusScopeId(id: string, isGroup?: boolean)
639```
640
641设置当前容器组件的id标识,设置当前容器组件是否为焦点组。焦点组与tabIndex不能混用。
642
643```ts
644// focusTest.ets
645@Entry
646@Component
647struct FocusableExample {
648  @State inputValue: string = ''
649
650  build() {
651    Scroll() {
652      Row({ space: 20 }) {
653        Column({ space: 20 }) {  // 标记为Column1
654          Column({ space: 5 }) {
655            Button('Group1')
656              .width(165)
657              .height(40)
658              .fontColor(Color.White)
659            Row({ space: 5 }) {
660              Button()
661                .width(80)
662                .height(40)
663                .fontColor(Color.White)
664              Button()
665                .width(80)
666                .height(40)
667                .fontColor(Color.White)
668            }
669            Row({ space: 5 }) {
670              Button()
671                .width(80)
672                .height(40)
673                .fontColor(Color.White)
674              Button()
675                .width(80)
676                .height(40)
677                .fontColor(Color.White)
678            }
679          }.borderWidth(2).borderColor(Color.Red).borderStyle(BorderStyle.Dashed)
680          Column({ space: 5 }) {
681            Button('Group2')
682              .width(165)
683              .height(40)
684              .fontColor(Color.White)
685            Row({ space: 5 }) {
686              Button()
687                .width(80)
688                .height(40)
689                .fontColor(Color.White)
690              Button()
691                .width(80)
692                .height(40)
693                .fontColor(Color.White)
694                .focusScopePriority('ColumnScope1', FocusPriority.PRIOR)  // Column1首次获焦时获焦
695            }
696            Row({ space: 5 }) {
697              Button()
698                .width(80)
699                .height(40)
700                .fontColor(Color.White)
701              Button()
702                .width(80)
703                .height(40)
704                .fontColor(Color.White)
705            }
706          }.borderWidth(2).borderColor(Color.Green).borderStyle(BorderStyle.Dashed)
707        }
708        .focusScopeId('ColumnScope1')
709        Column({ space: 5 }) {  // 标记为Column2
710          TextInput({placeholder: 'input', text: this.inputValue})
711            .onChange((value: string) => {
712              this.inputValue = value
713            })
714            .width(156)
715          Button('Group3')
716            .width(165)
717            .height(40)
718            .fontColor(Color.White)
719          Row({ space: 5 }) {
720            Button()
721              .width(80)
722              .height(40)
723              .fontColor(Color.White)
724            Button()
725              .width(80)
726              .height(40)
727              .fontColor(Color.White)
728          }
729          Button()
730            .width(165)
731            .height(40)
732            .fontColor(Color.White)
733            .focusScopePriority('ColumnScope2', FocusPriority.PREVIOUS)  // Column2获焦时获焦
734          Row({ space: 5 }) {
735            Button()
736              .width(80)
737              .height(40)
738              .fontColor(Color.White)
739            Button()
740              .width(80)
741              .height(40)
742              .fontColor(Color.White)
743          }
744          Button()
745            .width(165)
746            .height(40)
747            .fontColor(Color.White)
748          Row({ space: 5 }) {
749            Button()
750              .width(80)
751              .height(40)
752              .fontColor(Color.White)
753            Button()
754              .width(80)
755              .height(40)
756              .fontColor(Color.White)
757          }
758        }.borderWidth(2).borderColor(Color.Orange).borderStyle(BorderStyle.Dashed)
759        .focusScopeId('ColumnScope2', true)  // Column2为焦点组
760      }.alignItems(VerticalAlign.Top)
761    }
762  }
763}
764```
765
766
767![focus-3](figures/focus-3.gif)
768
769
770
771上述示例包含以下2步:
772
773- input方框内设置了焦点组,因此按下TAB键后焦点会快速从input中走出去,而按下方向键后可以在input内走焦。
774- 左上角的Column没有设置焦点组,因此只能通过Tab键一个一个地走焦。
775
776## 焦点与按键事件
777
778当组件获焦且存在点击事件(`onClick`)或单指单击事件(`TapGesture`)时,回车和空格会触发对应的事件回调。
779
780>  **说明:**
781>
782>  1. 点击事件(`onClick`)或单指单击事件(`TapGesture`)在回车、空格触发对应事件回调时,默认不冒泡传递,即父组件对应[按键事件](../reference/apis-arkui/arkui-ts/ts-universal-events-key.md)不会被同步触发。
783>  2. 按键事件(`onKeyEvent`)默认冒泡传递,即同时会触发父组件的按键事件回调。
784>  3. 组件同时存在点击事件(`onClick`)和按键事件(`onKeyEvent`),在回车、空格触发时,两者都会响应。
785>  4. 获焦组件响应点击事件(`onClick`),与焦点激活态无关。
786
787```
788@Entry
789@Component
790struct FocusOnclickExample {
791  @State count: number = 0
792  @State name: string = 'Button'
793
794  build() {
795    Column() {
796      Button(this.name)
797        .fontSize(30)
798        .onClick(() => {
799          this.count++
800          if (this.count <= 0) {
801            this.name = "count is negative number"
802          } else if (this.count % 2 === 0) {
803            this.name = "count is even number"
804          } else {
805            this.name = "count is odd number"
806          }
807        }).height(60)
808    }.height('100%').width('100%').justifyContent(FlexAlign.Center)
809  }
810}
811```
812![focus-4](figures/focus-4.gif)
813
814## 组件获焦能力说明
815
816
817  **表1** 基础组件获焦能力
818
819| 基础组件                                     | 是否有获焦能力 | focusable默认值 |
820| ---------------------------------------- | ------- | ------------ |
821| [AlphabetIndexer](../reference/apis-arkui/arkui-ts/ts-container-alphabet-indexer.md) | 是       | true         |
822| [Blank](../reference/apis-arkui/arkui-ts/ts-basic-components-blank.md) | 否       | false        |
823| [Button](../reference/apis-arkui/arkui-ts/ts-basic-components-button.md) | 是       | true         |
824| [CalendarPicker](../reference/apis-arkui/arkui-ts/ts-basic-components-calendarpicker.md) | 是       | true         |
825| [Checkbox](../reference/apis-arkui/arkui-ts/ts-basic-components-checkbox.md) | 是       | true         |
826| [CheckboxGroup](../reference/apis-arkui/arkui-ts/ts-basic-components-checkboxgroup.md) | 是       | true         |
827| [ContainerSpan](../reference/apis-arkui/arkui-ts/ts-basic-components-containerspan.md) | 否       | false         |
828| [DataPanel](../reference/apis-arkui/arkui-ts/ts-basic-components-datapanel.md) | 是       | false        |
829| [DatePicker](../reference/apis-arkui/arkui-ts/ts-basic-components-datepicker.md) | 是       | true         |
830| [Divider](../reference/apis-arkui/arkui-ts/ts-basic-components-divider.md) | 是       | false        |
831| [Gauge](../reference/apis-arkui/arkui-ts/ts-basic-components-gauge.md) | 是       | false        |
832| [Image](../reference/apis-arkui/arkui-ts/ts-basic-components-image.md) | 是       | false        |
833| [ImageAnimator](../reference/apis-arkui/arkui-ts/ts-basic-components-imageanimator.md) | 否       | false        |
834| [ImageSpan](../reference/apis-arkui/arkui-ts/ts-basic-components-imagespan.md)                 | 否       | false        |
835| [LoadingProgress](../reference/apis-arkui/arkui-ts/ts-basic-components-loadingprogress.md) | 是       | true        |
836| [Marquee](../reference/apis-arkui/arkui-ts/ts-basic-components-marquee.md) | 否       | false        |
837| [Menu](../reference/apis-arkui/arkui-ts/ts-basic-components-menu.md) | 是       | true         |
838| [MenuItem](../reference/apis-arkui/arkui-ts/ts-basic-components-menuitem.md) | 是       | true         |
839| [MenuItemGroup](../reference/apis-arkui/arkui-ts/ts-basic-components-menuitemgroup.md) | 否       | false         |
840| [Navigation](../reference/apis-arkui/arkui-ts/ts-basic-components-navigation.md) | 是       | true       |
841| [NavRouter](../reference/apis-arkui/arkui-ts/ts-basic-components-navrouter.md) | 否       | false        |
842| [NavDestination](../reference/apis-arkui/arkui-ts/ts-basic-components-navdestination.md) | 是       | true        |
843| [PatternLock](../reference/apis-arkui/arkui-ts/ts-basic-components-patternlock.md) | 是       | true        |
844| [Progress](../reference/apis-arkui/arkui-ts/ts-basic-components-progress.md) | 是       | true        |
845| [QRCode](../reference/apis-arkui/arkui-ts/ts-basic-components-qrcode.md) | 是       | true        |
846| [Radio](../reference/apis-arkui/arkui-ts/ts-basic-components-radio.md) | 是       | true         |
847| [Rating](../reference/apis-arkui/arkui-ts/ts-basic-components-rating.md) | 是       | true         |
848| [RichEditor](../reference/apis-arkui/arkui-ts/ts-basic-components-richeditor.md) | 是       | true         |
849| [RichText](../reference/apis-arkui/arkui-ts/ts-basic-components-richtext.md) | 否       | false        |
850| [ScrollBar](../reference/apis-arkui/arkui-ts/ts-basic-components-scrollbar.md) | 否       | false        |
851| [Search](../reference/apis-arkui/arkui-ts/ts-basic-components-search.md) | 是       | true         |
852| [Select](../reference/apis-arkui/arkui-ts/ts-basic-components-select.md) | 是       | true         |
853| [Slider](../reference/apis-arkui/arkui-ts/ts-basic-components-slider.md) | 是       | true         |
854| [Span](../reference/apis-arkui/arkui-ts/ts-basic-components-span.md) | 否       | false        |
855| [Stepper](../reference/apis-arkui/arkui-ts/ts-basic-components-stepper.md) | 是       | true         |
856| [StepperItem](../reference/apis-arkui/arkui-ts/ts-basic-components-stepperitem.md) | 是       | true         |
857| [SymbolSpan](../reference/apis-arkui/arkui-ts/ts-basic-components-symbolSpan.md) | 否       | false         |
858| [SymbolGlyph](../reference/apis-arkui/arkui-ts/ts-basic-components-symbolGlyph.md) | 否       | false         |
859| [Text](../reference/apis-arkui/arkui-ts/ts-basic-components-text.md) | 是       | false        |
860| [TextArea](../reference/apis-arkui/arkui-ts/ts-basic-components-textarea.md) | 否       | false         |
861| [TextClock](../reference/apis-arkui/arkui-ts/ts-basic-components-textclock.md) | 否       | false        |
862| [TextInput](../reference/apis-arkui/arkui-ts/ts-basic-components-textinput.md) | 是       | true         |
863| [TextPicker](../reference/apis-arkui/arkui-ts/ts-basic-components-textpicker.md) | 是       | true         |
864| [TextTimer](../reference/apis-arkui/arkui-ts/ts-basic-components-texttimer.md) | 否       | false        |
865| [TimePicker](../reference/apis-arkui/arkui-ts/ts-basic-components-timepicker.md) | 否       | false         |
866| [Toggle](../reference/apis-arkui/arkui-ts/ts-basic-components-toggle.md) | 是       | true         |
867| [XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md) | 是       | false        |
868
869  **表2** 容器组件获焦能力
870
871| 容器组件                                     | 是否可获焦 | focusable默认值 |
872| ---------------------------------------- | ----- | ------------ |
873| [Badge](../reference/apis-arkui/arkui-ts/ts-container-badge.md) | 否     | false        |
874| [Column](../reference/apis-arkui/arkui-ts/ts-container-column.md) | 是     | true         |
875| [ColumnSplit](../reference/apis-arkui/arkui-ts/ts-container-columnsplit.md) | 是     | true         |
876| [Counter](../reference/apis-arkui/arkui-ts/ts-container-counter.md) | 是     | false         |
877| [EmbeddedComponent](../reference/apis-arkui/arkui-ts/ts-container-embedded-component.md)    | 否     | false         |
878| [Flex](../reference/apis-arkui/arkui-ts/ts-container-flex.md) | 是     | true         |
879| [FlowItem](../reference/apis-arkui/arkui-ts/ts-container-flowitem.md)             | 是     | true         |
880| [FolderStack](../reference/apis-arkui/arkui-ts/ts-container-folderstack.md)             | 是     | true         |
881| [FormLink](../reference/apis-arkui/arkui-ts/ts-container-formlink.md)               | 否     | false         |
882| [GridCol](../reference/apis-arkui/arkui-ts/ts-container-gridcol.md) | 是     | true         |
883| [GridRow](../reference/apis-arkui/arkui-ts/ts-container-gridrow.md) | 是     | true         |
884| [Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md) | 是     | true         |
885| [GridItem](../reference/apis-arkui/arkui-ts/ts-container-griditem.md) | 是     | true         |
886| [Hyperlink](../reference/apis-arkui/arkui-ts/ts-container-hyperlink.md)         | 是     | true         |
887| [List](../reference/apis-arkui/arkui-ts/ts-container-list.md) | 是     | true         |
888| [ListItem](../reference/apis-arkui/arkui-ts/ts-container-listitem.md) | 是     | true         |
889| [ListItemGroup](../reference/apis-arkui/arkui-ts/ts-container-listitemgroup.md) | 是     | true         |
890| [Navigator](../reference/apis-arkui/arkui-ts/ts-container-navigator.md) | 是     | true         |
891| [Refresh](../reference/apis-arkui/arkui-ts/ts-container-refresh.md) | 是     | true        |
892| [RelativeContainer](../reference/apis-arkui/arkui-ts/ts-container-relativecontainer.md) | 否     | false         |
893| [Row](../reference/apis-arkui/arkui-ts/ts-container-row.md) | 是    | true         |
894| [RowSplit](../reference/apis-arkui/arkui-ts/ts-container-rowsplit.md) | 是     | true         |
895| [Scroll](../reference/apis-arkui/arkui-ts/ts-container-scroll.md) | 是     | true         |
896| [SideBarContainer](../reference/apis-arkui/arkui-ts/ts-container-sidebarcontainer.md) | 是     | true         |
897| [Stack](../reference/apis-arkui/arkui-ts/ts-container-stack.md) | 是     | true         |
898| [Swiper](../reference/apis-arkui/arkui-ts/ts-container-swiper.md) | 是     | true         |
899| [Tabs](../reference/apis-arkui/arkui-ts/ts-container-tabs.md) | 是     | true         |
900| [TabContent](../reference/apis-arkui/arkui-ts/ts-container-tabcontent.md) | 是     | true         |
901| [WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md)         | 否     | false         |
902| [WithTheme](../reference/apis-arkui/arkui-ts/ts-container-with-theme.md)         | 是     | true         |
903
904  **表3** 媒体组件获焦能力
905
906| 媒体组件                                     | 是否可获焦 | focusable默认值 |
907| ---------------------------------------- | ----- | ------------ |
908| [Video](../reference/apis-arkui/arkui-ts/ts-media-components-video.md) | 是     | true         |