• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 支持焦点处理
2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @jiangtao92-->
5<!--Designer: @piggyguy-->
6<!--Tester: @songyanhong-->
7<!--Adviser: @HelloCrease-->
8
9## 基础概念与规范
10
11### 基础概念
12
13**焦点、焦点链和走焦**
14
15- 焦点:指向当前应用界面上唯一的一个可交互元素,当用户使用键盘、电视遥控器、车机摇杆/旋钮等非指向性输入设备与应用程序进行间接交互时,基于焦点的导航和交互是重要的输入手段。
16- 焦点链:在应用的组件树形结构中,当一个组件获得焦点时,从根节点到该组件节点的整条路径上的所有节点都会被视为处于焦点状态,形成一条连续的焦点链。
17- 走焦:指焦点在应用内的组件之间转移的行为。这一过程对用户是透明的,但开发者可以通过监听onFocus(焦点获取)和onBlur(焦点失去)事件来捕捉这些变化。关于走焦的具体方式和规则,详见[走焦规范](#走焦规范)。
18
19
20**焦点激活态**
21
22用来指向当前获焦组件的样式。
23
24- 显示规则:默认情况下焦点激活态不会显示,只有当应用进入激活态后,焦点激活态才会显示。因此,虽然获得焦点的组件不一定显示焦点激活态(取决于是否处于激活态),但显示焦点激活态的组件必然是获得焦点的。大部分组件内置了焦点激活态样式,开发者同样可以使用样式接口进行自定义,一旦自定义,组件将不再显示内置的焦点激活态样式。关于焦点激活态样式设置的具体方式,详见[焦点样式](#焦点样式)。在焦点链中,若多个组件同时拥有焦点激活态,系统将采用子组件优先的策略,优先显示子组件的焦点激活态,并且仅显示一个焦点激活态。
25- 进入激活态:使用外接键盘按下Tab键/使用FocusController的activate(true)方法才会进入焦点的激活态,进入激活态后,才可以使用键盘Tab键/方向键进行走焦。首次用来激活焦点激活态的Tab键不会触发走焦。
26- 退出激活态:当应用收到FocusController的active(false)方法/点击事件时(包括手指触屏的按下事件和鼠标左键的按下事件),焦点的激活态会退出。
27
28```ts
29@Entry
30@Component
31struct FocusActiveExample {
32  build() {
33    Column() {
34      Button('Set Active').width(140).height(45).margin(5).onClick(() => {
35        this.getUIContext().getFocusController().activate(true, true);
36      })
37      Button('Set Not Active').width(140).height(45).margin(5).onClick(() => {
38        this.getUIContext().getFocusController().activate(false, true);
39      })
40    }.width('100%')
41  }
42}
43```
44
45
46按下Tab键,焦点激活态显示。点击鼠标退出焦点激活态。
47
48![Active_Focus_1](figures/Active_Focus_1.gif)
49
50
51调用[activate](../reference/apis-arkui/arkts-apis-uicontext-focuscontroller.md#activate14)接口进入和退出焦点激活态。
52
53![Active_Focus_2](figures/Active_Focus_2.gif)
54
55示例操作步骤:
561. 点击Set Active按钮,调用[activate](../reference/apis-arkui/arkts-apis-uicontext-focuscontroller.md#activate14)接口进入焦点激活态。
572. Tab走焦至Set Not Active按钮,Enter键触发按键事件,调用[activate](../reference/apis-arkui/arkts-apis-uicontext-focuscontroller.md#activate14)接口退出焦点激活态。
58
59**层级页面**
60
61层级页面是焦点框架中特定容器组件的统称,涵盖Page、Dialog、SheetPage、ModalPage、Menu、Popup、NavBar、NavDestination等。这些组件通常具有以下关键特性:
62
63- 视觉层级独立性:从视觉呈现上看,这些组件独立于其他页面内容,并通常位于其上方,形成视觉上的层级差异。
64- 焦点跟随:此类组件在首次创建并展示之后,会立即将应用内焦点抢占。
65- 走焦范围限制:当焦点位于这些组件内部时,用户无法通过键盘按键将焦点转移到组件外部的其他元素上,焦点移动仅限于组件内部。
66
67在一个应用程序中,任何时候都至少存在一个层级页面组件,并且该组件会持有当前焦点。当该层级页面关闭或不再可见时,焦点会自动转移到下一个可用的层级页面组件上,确保用户交互的连贯性和一致性。
68
69> **说明:**
70>
71> Popup组件在focusable属性(组件属性,非通用属性)为false的时候,不会有第2条特性。
72>
73> NavBar、NavDestination没有第3条特性,对于它们的走焦范围,是与它们的首个父层级页面相同的。
74
75**根容器**
76
77根容器是[层级页面](#基础概念)内的概念,当某个[层级页面](#基础概念)首次创建并展示时,根据[层级页面](#基础概念)的特性,焦点会立即被该[层级页面](#基础概念)抢占。此时,该[层级页面](#基础概念)所在焦点链的末端节点将成为默认焦点,而这个默认焦点通常位于该[层级页面](#基础概念)的根容器上。
78
79在缺省状态下,[层级页面](#基础概念)的默认焦点位于其根容器上,但开发者可以通过defaultFocus属性来自定义这一行为。
80
81当焦点位于根容器时,首次按下Tab键不仅会使焦点进入激活状态,还会触发焦点向子组件的传递。如果子组件本身也是一个容器,则焦点会继续向下传递,直至到达叶子节点。传递规则是:优先传递给上一次获得焦点的子节点,如果不存在这样的节点,则默认传递给第一个子节点。
82
83### 走焦规范
84
85根据走焦的触发方式,可以分为主动走焦和被动走焦。
86
87**主动走焦**
88
89
90指开发者/用户主观行为导致的焦点移动,包括:使用外接键盘的按键走焦(Tab键/Shift+Tab键/方向键)、使用requestFocus申请焦点、clearFocus清除焦点、focusOnTouch点击申请焦点等接口导致的焦点转移。
91
92
93- 按键走焦
941. 前提:当前应用需处于焦点激活态。
952. 范围限制:按键走焦仅在当前获得焦点的层级页面内进行,具体参见“层级页面”中的“走焦范围限制”部分。
963. 按键类型:
97Tab键:遵循Z字型遍历逻辑,完成当前范围内所有叶子节点的遍历,到达当前范围内的最后一个组件后,继续按下Tab键,焦点将循环至范围内的第一个可获焦组件,实现循环走焦。
98Shift+Tab键:与Tab键具有相反的焦点转移效果。
99方向键(上、下、左、右):遵循十字型移动策略,在单层容器中,焦点的转移由该容器的特定走焦算法决定。若算法判定下一个焦点应落在某个容器组件上,系统将采用中心点距离优先的算法来进一步确定容器内的目标子节点。
1004. 走焦算法:每个可获焦的容器组件都有其特定的走焦算法,用于定义焦点转移的规则。
1015. 子组件优先:当子组件处理按键走焦事件,父组件将不再介入。
102
103- requestFocus
104详见[主动获焦失焦](#主动获焦失焦),可以主动将焦点转移到指定组件上。
105不可跨窗口或跨ArkUI实例申请焦点,但可以跨层级页面申请焦点。
106
107- clearFocus
108详见[clearFocus](../reference/apis-arkui/arkts-apis-uicontext-focuscontroller.md#clearfocus12),会清除当前层级页面中的焦点,最终焦点停留在根容器上。
109
110- focusOnTouch
111详见[focusOnTouch](../reference/apis-arkui/arkui-ts/ts-universal-attributes-focus.md#focusontouch9),使绑定组件具备点击后获得焦点的能力。若组件本身不可获焦,则此功能无效。若绑定的是容器组件,点击后优先将焦点转移给上一次获焦的子组件,否则转移给第一个可获焦的子组件。
112
113
114**被动走焦**
115
116被动走焦是指组件焦点因系统或其他操作而自动转移,无需开发者直接干预,这是焦点系统的默认行为。
117
118
119目前会被动走焦的机制有:
120
121- 组件删除:当处于焦点状态的组件被删除时,焦点框架首先尝试将焦点转移到相邻的兄弟组件上,遵循先向后再向前的顺序。若所有兄弟组件均不可获焦,则焦点将释放,并通知其父组件进行焦点处理。
122- 属性变更:若将处于焦点状态的组件的focusable或enabled属性设置为false,或者将visibility属性设置为不可见,系统将自动转移焦点至其他可获焦组件,转移方式与1中相同。
123- [层级页面](#基础概念)切换:当发生[层级页面](#基础概念)切换时,如从一个[层级页面](#基础概念)跳转到另一个[层级页面](#基础概念),当前[层级页面](#基础概念)的焦点将自动释放,新[层级页面](#基础概念)可能会根据预设逻辑自动获得焦点。
124- Web组件初始化:对于Web组件,当其被创建时,若其设计需要立即获得焦点(如某些弹出框或输入框),则可能触发焦点转移至该Web组件,其行为属于组件自身的行为逻辑,不属于焦点框架的规格范围。
125
126### 走焦算法
127
128在焦点管理系统中,每个可获焦的容器都配备有特定的走焦算法,这些算法定义了当使用Tab键、Shift+Tab键或方向键时,焦点如何从当前获焦的子组件转移到下一个可获焦的子组件。
129
130容器采用何种走焦算法取决于其UX(用户体验)规格,并由容器组件进行适配。目前,焦点框架支持三种走焦算法:线性走焦、投影走焦和自定义走焦。
131
132**线性走焦算法**
133
134
135线性走焦算法是默认的走焦策略,它基于容器中子节点在节点树中的挂载顺序进行走焦,常用于单方向布局的容器,如Row、Column和Flex容器。运行规则如下:
136
137
138- 顺序依赖:走焦顺序完全基于子节点在节点树中的挂载顺序,与它们在界面上的实际布局位置无关。
139- Tab键走焦:使用Tab键时,焦点将按照子节点的挂载顺序依次遍历。
140- 方向键走焦:当使用与容器定义方向垂直的方向键时,容器不接受该方向的走焦请求。例如,在横向的Row容器中,无法使用方向键进行上下移动。
141- 边界处理:当焦点位于容器的首尾子节点时,容器将拒绝与当前焦点方向相反的方向键走焦请求。例如,焦点在一个横向的Row容器的第一个子节点上时,该容器无法处理方向键左的走焦请求。
142
143```ts
144@Entry
145@Component
146struct FocusLinerExample {
147  build() {
148    Column() {
149      Column() {
150        Button("Column Button1")
151          .width(150)
152          .height(45)
153          .fontColor(Color.White)
154          .margin(10)
155        Button("Column Button2")
156          .width(150)
157          .height(45)
158          .fontColor(Color.White)
159          .margin(10)
160      }
161      .margin(10)
162
163      Row() {
164        Button("Row Button1")
165          .width(150)
166          .height(45)
167          .fontColor(Color.White)
168          .margin(10)
169        Button("Row Button2")
170          .width(150)
171          .height(45)
172          .fontColor(Color.White)
173          .margin(10)
174      }
175    }
176  }
177}
178```
179
180Tab键走焦:按照子节点的挂载顺序循环走焦。
181
182![Liner_Focus_1](figures/Liner_Focus_1.gif)
183
184方向键上下走焦:纵向的Column容器中,可以使用上下键走焦,无法使用左右键走焦。
185
186![Liner_Focus_1](figures/Liner_Focus_2.gif)
187
188横向的Row容器中,可以使用左右键走焦,无法使用上下键走焦。
189
190![Liner_Focus_1](figures/Liner_Focus_3.gif)
191
192
193**投影走焦算法**
194
195投影走焦算法基于当前获焦组件在走焦方向上的投影,结合子组件与投影的重叠面积和中心点距离进行胜出判定。该算法适用于子组件大小不一的容器,目前仅支持配置了wrap属性的Flex组件。运行规则如下:
196
197
198- 方向键走焦时,判断投影与子组件区域的重叠面积,在所有面积不为0的子组件中,计算它们与当前获焦组件的中心点直线距离,选择距离最短的子组件。若存在多个备选子组件,则选择节点树上更靠前的子组件。若无任何子组件与投影有重叠,说明该容器无法处理该方向键的走焦请求。
199- Tab键走焦时,先使用规格1,按照方向键右进行判定,若找到则成功退出,若无法找到,则将当前获焦子组件的位置模拟往下移动该获焦子组件的高度,然后再按照方向键左进行投影判定,有投影重叠且中心点直线距离最近的子组件胜出,若无投影重叠的子组件,则表示该容器无法处理本次Tab键走焦请求。
200- Shift+Tab键走焦时,先使用规格1,按照方向键左进行判定,找到则成功退出。若无法找到,则将当前获焦子组件的位置模拟向上移动该获焦子组件的高度,然后再按照方向键右进行投影判定,有投影重叠且中心点直线距离最近的子组件胜出,若无投影重叠的子组件,则表示该容器无法处理本次的Shift+Tab键走焦请求。
201
202```ts
203@Entry
204@Component
205struct ProjectAreaFocusExample {
206  build() {
207    Column() {
208      Column({ space: 5 }) {
209        Text('Wrap').fontSize(12).width('90%')
210        // 子组件多行布局
211        Flex({ wrap: FlexWrap.Wrap }) {
212          Button('1').width(140).height(50).margin(5)
213          Button('2').width(140).height(50).margin(5)
214          Button('3').width(140).height(50).margin(5)
215          Button('4').width(140).height(50).margin(5)
216          Button('5').width(140).height(50).margin(5)
217        }
218        .width('90%')
219        .padding(10)
220      }.width('100%').margin({ top: 5 })
221    }.width('100%')
222  }
223}
224```
225
226> **说明:**
227>
228> - 这种投影聚焦算法计算的聚焦顺序与组件布局和大小密切相关,建议在组件排列非常规整的场景下使用。如果组件大小不一且存在横向或纵向的交叠关系,则可能会导致聚焦顺序与开发者预期不符。
229> - 如果开发者希望有明确的走焦顺序,建议使用Column/Row等顺序走焦的容器实现。
230
231Flex多行组件布局,组件大小一致,走焦正常。
232
233![Project_Area_Focus_1](figures/Project_Area_Focus_1.gif)
234
235```ts
236@Entry
237@Component
238struct ProjectAreaFocusExample2 {
239  build() {
240    Column() {
241      Column({ space: 5 }) {
242        Text('Wrap').fontSize(12).width('90%')
243        // 子组件多行布局
244        Flex({ wrap: FlexWrap.Wrap }) {
245          Button('1').width(145).height(50).margin(5)
246          Button('2').width(145).height(50).margin(5)
247          Button('3').width(150).height(50).margin(5)
248          Button('4').width(160).height(50).margin(5)
249          Button('5').width(170).height(50).margin(5)
250        }
251        .width('90%')
252        .padding(10)
253      }.width('100%').margin({ top: 5 })
254    }.width('100%')
255  }
256}
257```
258
259Flex多行组件布局,组件大小不一且有纵向的交叠关系,无法Tab走焦至下方3、4、5按钮组件。
260
261![Project_Area_Focus_2](figures/Project_Area_Focus_2.gif)
262
263
264**自定义走焦算法**
265
266由组件自定义的走焦算法,规格由组件定义。
267
268## 获焦/失焦事件
269
270```ts
271onFocus(event: () => void)
272```
273
274
275获焦事件回调,绑定该接口的组件获焦时,回调响应。
276
277```ts
278onBlur(event:() => void)
279```
280
281失焦事件回调,绑定该接口的组件失焦时,回调响应。
282
283onFocus和onBlur两个接口通常成对使用,来监听组件的焦点变化。
284
285```ts
286// xxx.ets
287@Entry
288@Component
289struct FocusEventExample {
290  @State oneButtonColor: Color = Color.Gray;
291  @State twoButtonColor: Color = Color.Gray;
292  @State threeButtonColor: Color = Color.Gray;
293
294  build() {
295    Column({ space: 20 }) {
296      // 通过外接键盘的上下键可以让焦点在三个按钮间移动,按钮获焦时颜色变化,失焦时变回原背景色
297      Button('First Button')
298        .width(260)
299        .height(70)
300        .backgroundColor(this.oneButtonColor)
301        .fontColor(Color.Black)
302          // 监听第一个组件的获焦事件,获焦后改变颜色
303        .onFocus(() => {
304          this.oneButtonColor = Color.Green;
305        })
306          // 监听第一个组件的失焦事件,失焦后改变颜色
307        .onBlur(() => {
308          this.oneButtonColor = Color.Gray;
309        })
310
311      Button('Second Button')
312        .width(260)
313        .height(70)
314        .backgroundColor(this.twoButtonColor)
315        .fontColor(Color.Black)
316          // 监听第二个组件的获焦事件,获焦后改变颜色
317        .onFocus(() => {
318          this.twoButtonColor = Color.Green;
319        })
320          // 监听第二个组件的失焦事件,失焦后改变颜色
321        .onBlur(() => {
322          this.twoButtonColor = Color.Gray;
323        })
324
325      Button('Third Button')
326        .width(260)
327        .height(70)
328        .backgroundColor(this.threeButtonColor)
329        .fontColor(Color.Black)
330          // 监听第三个组件的获焦事件,获焦后改变颜色
331        .onFocus(() => {
332          this.threeButtonColor = Color.Green;
333        })
334          // 监听第三个组件的失焦事件,失焦后改变颜色
335        .onBlur(() => {
336          this.threeButtonColor = Color.Gray ;
337        })
338    }.width('100%').margin({ top: 20 })
339  }
340}
341```
342
343
344![zh-cn_image_0000001511740584](figures/zh-cn_image_0000001511740584.gif)
345
346
347上述示例包含以下3步:
348
349- 应用打开,按下Tab键激活走焦,“First Button”显示焦点激活态样式:组件外围有一个蓝色的闭合框,onFocus回调响应,背景色变成绿色。
350- 按下Tab键,触发走焦,“Second Button”获焦,onFocus回调响应,背景色变成绿色;“First Button”失焦,onBlur回调响应,背景色变回灰色。
351- 按下Tab键,触发走焦,“Third Button”获焦,onFocus回调响应,背景色变成绿色;“Second Button”失焦,onBlur回调响应,背景色变回灰色。
352
353父子节点同时存在获焦和失焦事件时,获焦/失焦事件响应顺序为:
354
355父节点Row1失焦 —> 子节点Button1失焦 —> 子节点Button2获焦 —> 父节点Row2获焦。
356
357```ts
358@Entry
359@Component
360struct FocusAndBlurExample {
361  build() {
362    Column() {
363      Column({ space: 5 }) {
364        Row() { // 父节点Row1
365          Button('Button1') // 子节点Button1
366            .width(140)
367            .height(45)
368            .margin(5)
369            .onFocus(() => {
370              console.info("Button1 onFocus");
371            })
372            .onBlur(() => {
373              console.info("Button1 onBlur");
374            })
375        }
376        .onFocus(() => {
377          console.info("Row1 onFocus");
378        })
379        .onBlur(() => {
380          console.info("Row1 onBlur");
381        })
382
383        Row() { // 父节点Row2
384          Button('Button2') // 子节点Button2
385            .width(140)
386            .height(45)
387            .margin(5)
388            .onFocus(() => {
389              console.info("Button2 onFocus");
390            })
391            .onBlur(() => {
392              console.info("Button2 onBlur");
393            })
394        }
395        .onFocus(() => {
396          console.info("Row2 onFocus");
397        })
398        .onBlur(() => {
399          console.info("Row2 onBlur");
400        })
401      }.width('100%').margin({ top: 5 })
402    }.width('100%')
403  }
404}
405```
406
407Button1走焦到Button2,日志打印顺序:
408```ts
409Row1 onBlur
410Button1 onBlur
411Button2 onFocus
412Row2 onFocus
413```
414
415## 设置组件是否可获焦
416
417```ts
418focusable(value: boolean)
419```
420
421设置组件是否可获焦。
422
423按照组件的获焦能力可大致分为三类:
424
425- 默认可获焦的组件,通常是有交互行为的组件,例如Button、Checkbox、TextInput组件,此类组件无需设置任何属性,默认即可获焦。
426
427- 有获焦能力,但默认不可获焦的组件,典型的是Text、Image组件,此类组件缺省情况下无法获焦,若需要使其获焦,可使用通用属性focusable(true)使能。对于没有配置focusable属性,有获焦能力但默认不可获焦的组件,例如没有可获焦子组件的容器组件,为其配置onClick或是单指单击的Tap手势,该组件会隐式地成为可获焦组件。如果其focusable属性被设置为false,即使配置了上述事件,该组件依然不可获焦。
428
429- 无获焦能力的组件,通常是无任何交互行为的展示类组件,例如Blank、Circle组件,此类组件即使使用focusable属性也无法使其可获焦。
430
431
432```ts
433enabled(value: boolean)
434```
435
436设置组件可交互性属性[enabled](../reference/apis-arkui/arkui-ts/ts-universal-attributes-enable.md#enabled)为`false`,则组件不可交互,无法获焦。
437
438```ts
439visibility(value: Visibility)
440```
441
442设置组件可见性属性[visibility](../reference/apis-arkui/arkui-ts/ts-universal-attributes-visibility.md#visibility)为`Visibility.None`或`Visibility.Hidden`,则组件不可见,无法获焦。
443
444```ts
445focusOnTouch(value: boolean)
446```
447
448设置当前组件是否支持点击获焦能力。
449
450> **说明:**
451>
452>当某组件处于获焦状态时,将其的focusable属性或enabled属性设置为false,会自动使该组件失焦,然后焦点按照[走焦规范](#走焦规范)将焦点转移给其他组件。
453
454```ts
455// xxx.ets
456@Entry
457@Component
458struct FocusableExample {
459  @State textFocusable: boolean = true;
460  @State textEnabled: boolean = true;
461  @State color1: Color = Color.Yellow;
462  @State color2: Color = Color.Yellow;
463  @State color3: Color = Color.Yellow;
464
465  build() {
466    Column({ space: 5 }) {
467      Text('Default Text')    // 第一个Text组件未设置focusable属性,默认不可获焦
468        .borderColor(this.color1)
469        .borderWidth(2)
470        .width(300)
471        .height(70)
472        .onFocus(() => {
473          this.color1 = Color.Blue;
474        })
475        .onBlur(() => {
476          this.color1 = Color.Yellow;
477        })
478      Divider()
479
480      Text('focusable: ' + this.textFocusable)    // 第二个Text设置了focusable初始为true,focusableOnTouch为true
481        .borderColor(this.color2)
482        .borderWidth(2)
483        .width(300)
484        .height(70)
485        .focusable(this.textFocusable)
486        .focusOnTouch(true)
487        .onFocus(() => {
488          this.color2 = Color.Blue;
489        })
490        .onBlur(() => {
491          this.color2 = Color.Yellow;
492        })
493
494      Text('enabled: ' + this.textEnabled)    // 第三个Text设置了focusable为true,enabled初始为true
495        .borderColor(this.color3)
496        .borderWidth(2)
497        .width(300)
498        .height(70)
499        .focusable(true)
500        .enabled(this.textEnabled)
501        .focusOnTouch(true)
502        .onFocus(() => {
503          this.color3 = Color.Blue;
504        })
505        .onBlur(() => {
506          this.color3 = Color.Yellow;
507        })
508
509      Divider()
510
511      Row() {
512        Button('Button1')
513          .width(140).height(70)
514        Button('Button2')
515          .width(160).height(70)
516      }
517
518      Divider()
519      Button('Button3')
520        .width(300).height(70)
521
522      Divider()
523    }.width('100%').justifyContent(FlexAlign.Center)
524    .onKeyEvent((e) => {
525      // 绑定onKeyEvent,在该Column组件获焦时,按下'F'键,可将第二个Text的focusable置反
526      if (e.keyCode === 2022 && e.type === KeyType.Down) {
527        this.textFocusable = !this.textFocusable;
528      }
529      // 绑定onKeyEvent,在该Column组件获焦时,按下'G'键,可将第三个Text的enabled置反
530      if (e.keyCode === 2023 && e.type === KeyType.Down) {
531        this.textEnabled = !this.textEnabled;
532      }
533    })
534  }
535}
536```
537
538
539运行效果:
540
541
542![focus-1.gif](figures/focus-1.gif)
543
544上述示例包含以下3步:
545
546- 第一个Text组件没有设置focusable(true)属性,该Text组件无法获焦。
547- 点击第二个Text组件,由于设置了focusOnTouch(true),第二个组件获焦。按下Tab键,触发走焦,仍然是第二个Text组件获焦。按键盘F键,触发onKeyEvent,focusable置为false,第二个Text组件变成不可获焦,焦点自动转移,会自动从Text组件寻找下一个可获焦组件,焦点转移到第三个Text组件上。
548- 按键盘G键,触发onKeyEvent,enabled置为false,第三个Text组件变成不可获焦,焦点自动转移,使焦点转移到Row容器上,容器中使用的是默认配置,会转移到Button1上。
549
550## 设置容器绘制焦点框
551
552虽然容器组件本身可以获焦,但是无法绘制焦点框。可以为其配置onClick或是单指单击的Tap手势,在容器上绘制焦点框。
553
554> **说明:**
555>
556> 容器绘制焦点框前提:
557> - 容器内部没有可获焦子节点。
558> - 容器配置有onClick或是单指单击的Tap手势。
559> - 容器本身未设置focusable属性,或设置在onClick或是单指单击的Tap手势之后。
560
561```ts
562@Entry
563@Component
564struct ScopeFocusExample {
565  @State scopeFocusState: boolean = true;
566
567  build() {
568    Column() {
569      Column({ space: 5 }) {
570        Text("容器获焦").textAlign(TextAlign.Center)
571      }
572      .justifyContent(FlexAlign.Center)
573      .width('80%')
574      .height(50)
575      .margin({ top: 5, bottom: 5 })
576      .onClick(() => {
577      })
578      .focusable(this.scopeFocusState)
579
580      Button('Button1')
581        .width(140)
582        .height(45)
583        .margin(5)
584        .onClick(() => {
585          this.scopeFocusState = !this.scopeFocusState;
586          console.info("Button1 onFocus");
587        })
588      Button('Button2')
589        .width(140)
590        .height(45)
591        .margin(5)
592    }.width('100%')
593  }
594}
595```
596
597
598![Scope_Focus_1.gif](figures/Scope_Focus_1.gif)
599
600上述示例包含以下2步:
601
602- Column配置onClick事件并设置focusable为true后,Tab键走焦,Column容器可以绘制焦点框。
603- 点击Button1,将Column的focusable属性设置为false,Column容器无法获焦和绘制焦点框。
604
605## 设置焦点停留在容器上
606
607```ts
608tabStop(isTabStop: boolean)
609```
610设置当前容器组件的[tabStop](../reference/apis-arkui/arkui-ts/ts-universal-attributes-focus.md#tabstop14)属性,可决定在走焦时焦点是否会停留在当前容器。
611
612```ts
613@Entry
614@Component
615struct TabStopExample {
616  build() {
617    Column({ space: 20 }) {
618      Button('Button1')
619        .width(140)
620        .height(45)
621        .margin(5)
622      Column() {
623        Button('Button2')
624          .width(140)
625          .height(45)
626          .margin(5)
627        Button('Button3')
628          .width(140)
629          .height(45)
630          .margin(5)
631      }.tabStop(true)
632    }.width('100%')
633  }
634}
635```
636
637![TabStop_Focus_1.gif](figures/TabStop_Focus_1.gif)
638
639上述示例包含以下2步:
640
641- Column配置tabStop后,Tab键走焦,焦点在Button1和Column容器之间切换,Column容器可以绘制焦点框。
642- 走焦至Column容器后,按Enter键,焦点转移到容器中的第一个可获焦节点上。Tab键走焦,走焦至容器中其他可获焦节点。
643
644## 默认焦点
645
646### 层级页面的默认焦点
647
648```ts
649defaultFocus(value: boolean)
650```
651
652设置当前组件是否为当前[层级页面](#基础概念)上的默认焦点。
653
654
655```ts
656// xxx.ets
657@Entry
658@Component
659struct morenjiaodian {
660  @State oneButtonColor: Color = Color.Gray;
661  @State twoButtonColor: Color = Color.Gray;
662  @State threeButtonColor: Color = Color.Gray;
663
664  build() {
665    Column({ space: 20 }) {
666      // 通过外接键盘的上下键可以让焦点在三个按钮间移动,按钮获焦时颜色变化,失焦时变回原背景色
667      Button('First Button')
668        .width(260)
669        .height(70)
670        .backgroundColor(this.oneButtonColor)
671        .fontColor(Color.Black)
672          // 监听第一个组件的获焦事件,获焦后改变颜色
673        .onFocus(() => {
674          this.oneButtonColor = Color.Green;
675        })
676          // 监听第一个组件的失焦事件,失焦后改变颜色
677        .onBlur(() => {
678          this.oneButtonColor = Color.Gray;
679        })
680
681      Button('Second Button')
682        .width(260)
683        .height(70)
684        .backgroundColor(this.twoButtonColor)
685        .fontColor(Color.Black)
686          // 监听第二个组件的获焦事件,获焦后改变颜色
687        .onFocus(() => {
688          this.twoButtonColor = Color.Green;
689        })
690          // 监听第二个组件的失焦事件,失焦后改变颜色
691        .onBlur(() => {
692          this.twoButtonColor = Color.Gray;
693        })
694
695      Button('Third Button')
696        .width(260)
697        .height(70)
698        .backgroundColor(this.threeButtonColor)
699        .fontColor(Color.Black)
700          // 设置默认焦点
701        .defaultFocus(true)
702          // 监听第三个组件的获焦事件,获焦后改变颜色
703        .onFocus(() => {
704          this.threeButtonColor = Color.Green;
705        })
706          // 监听第三个组件的失焦事件,失焦后改变颜色
707        .onBlur(() => {
708          this.threeButtonColor = Color.Gray ;
709        })
710    }.width('100%').margin({ top: 20 })
711  }
712}
713```
714
715![defaultFocus.gif](figures/defaultFocus.gif)
716
717上述示例包含以下2步:
718
719- 在第三个Button组件上设置了defaultFocus(true),进入[层级页面](#基础概念)后第三个Button默认获焦,显示为绿色。
720- 按下Tab键,触发走焦,第三个Button正处于获焦状态,会出现焦点框。
721
722### 容器的默认焦点
723
724容器的默认焦点受到[获焦优先级](#焦点组与获焦优先级)的影响。
725
726**defaultFocus与FocusPriority的区别**
727
728[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获焦和高优先级组件获焦。
729
730示例
731
732```ts
733@Entry
734@Component
735struct Index {
736  build() {
737    Row() {
738      Button('Button1')
739        .defaultFocus(true)
740      Button('Button2')
741        .focusScopePriority('RowScope', FocusPriority.PREVIOUS)
742    }.focusScopeId('RowScope')
743  }
744}
745```
746
747### 层级页面/容器整体获焦时的焦点链
748
749**整体获焦与非整体获焦**
750
751- 整体获焦是[层级页面](#基础概念)/容器自身作为焦点链的叶节点获焦,获焦后再把焦点链叶节点转移到子孙组件。例如,[层级页面](#基础概念)切换、Navigation组件中的路由切换、焦点组走焦、容器组件主动调用requestFocusById等。
752
753- 非整体获焦是某个组件作为焦点链叶节点获焦,导致其祖先节点跟着获焦。例如TextInput组件主动获取焦点、Tab键在非焦点组场景下走焦等。
754
755**整体获焦的焦点链形成**
756
7571.[层级页面](#基础概念)首次获焦:
758
759- 焦点链叶节点为配置了defaultFocus的节点。
760
761- 未配置defaultFocus时,焦点停留在[层级页面](#基础概念)的根容器上。
762
7632.[层级页面](#基础概念)非首次获焦:由上次获焦的节点获焦。
764
7653.获焦链上存在配置了获焦优先级的组件和容器:
766
767- 容器内存在优先级大于PREVIOUS的组件,由优先级最高的组件获焦。
768
769- 容器内不存在优先级大于PREVIOUS的组件,由上次获焦的节点获焦。例如,窗口失焦后重新获焦。
770
771
772## 焦点样式
773
774> **说明:**
775>
776> 最终绘制焦点激活态的组件的[zIndex](../reference/apis-arkui/arkui-ts/ts-universal-attributes-z-order.md#zindex)默认会被抬升至INT_MAX,如果该组件已经配置了zIndex,则不做zIndex调整。该组件不再绘制焦点激活态时,例如组件失焦或是退出走焦态,zIndex恢复为默认层级。
777>
778
779```ts
780focusBox(style: FocusBoxStyle)
781```
782
783设置当前组件系统焦点框样式。
784
785```ts
786import { ColorMetrics, LengthMetrics } from '@kit.ArkUI'
787
788@Entry
789@Component
790struct RequestFocusExample {
791  build() {
792    Column({ space: 30 }) {
793      Button("small black focus box")
794        .focusBox({
795          margin: new LengthMetrics(0),
796          strokeColor: ColorMetrics.rgba(0, 0, 0),
797        })
798      Button("large red focus box")
799        .focusBox({
800          margin: LengthMetrics.px(20),
801          strokeColor: ColorMetrics.rgba(255, 0, 0),
802          strokeWidth: LengthMetrics.px(10)
803        })
804    }
805    .alignItems(HorizontalAlign.Center)
806    .width('100%')
807  }
808}
809```
810
811![focusBox](figures/focusBox.gif)
812
813
814上述示例包含以下2步:
815
816- 进入[层级页面](#基础概念),按下Tab键触发走焦,第一个Button获焦,焦点框样式为紧贴边缘的蓝色细框。
817- 按下Tab键,走焦到第二个Button,焦点框样式为远离边缘的红色粗框。
818
819## 主动获焦/失焦
820
821- 使用FocusController中的方法
822
823  更推荐使用FocusController中的requestFocus主动获取焦点。优势如下:
824  - 当前帧生效,避免被下一帧组件树变化影响。
825  - 有异常值返回,便于排查主动获取焦点失败的原因。
826  - 避免多实例场景中取到错误实例。
827
828  需先使用UIContext中的[getFocusController()](../reference/apis-arkui/arkts-apis-uicontext-uicontext.md#getfocuscontroller12)方法获取实例,再通过此实例调用对应方法。
829
830  ```ts
831  requestFocus(key: string): void
832  ```
833  通过组件的id将焦点转移到组件树对应的实体节点,生效时间为当帧生效。
834
835  ```ts
836  clearFocus(): void
837  ```
838  清除焦点,将焦点强制转移到层级页面根容器节点,焦点链路上其他节点失焦。
839
840- 使用focusControl中的方法
841  ```ts
842  requestFocus(value: string): boolean
843  ```
844
845  调用此接口可以主动让焦点转移至参数指定的组件上,焦点转移生效时间为下一个帧信号。
846
847
848```ts
849// focusTest.ets
850@Entry
851@Component
852struct RequestExample {
853  @State btColor: string = '#ff2787d9'
854  @State btColor2: string = '#ff2787d9'
855
856  build() {
857    Column({ space: 20 }) {
858      Column({ space: 5 }) {
859        Button('Button')
860          .width(200)
861          .height(70)
862          .fontColor(Color.White)
863          .focusOnTouch(true)
864          .backgroundColor(this.btColor)
865          .onFocus(() => {
866            this.btColor = '#ffd5d5d5'
867          })
868          .onBlur(() => {
869            this.btColor = '#ff2787d9'
870          })
871          .id("testButton")
872
873        Button('Button')
874          .width(200)
875          .height(70)
876          .fontColor(Color.White)
877          .focusOnTouch(true)
878          .backgroundColor(this.btColor2)
879          .onFocus(() => {
880            this.btColor2 = '#ffd5d5d5'
881          })
882          .onBlur(() => {
883            this.btColor2 = '#ff2787d9'
884          })
885          .id("testButton2")
886
887        Divider()
888          .vertical(false)
889          .width("80%")
890          .backgroundColor('#ff707070')
891          .height(10)
892
893        Button('FocusController.requestFocus')
894          .width(200).height(70).fontColor(Color.White)
895          .onClick(() => {
896            this.getUIContext().getFocusController().requestFocus("testButton")
897          })
898          .backgroundColor('#ff2787d9')
899
900        Button("focusControl.requestFocus")
901          .width(200).height(70).fontColor(Color.White)
902          .onClick(() => {
903            focusControl.requestFocus("testButton2")
904          })
905          .backgroundColor('#ff2787d9')
906
907        Button("clearFocus")
908          .width(200).height(70).fontColor(Color.White)
909          .onClick(() => {
910            this.getUIContext().getFocusController().clearFocus()
911          })
912          .backgroundColor('#ff2787d9')
913      }
914    }
915    .width('100%')
916    .height('100%')
917  }
918}
919```
920
921![focus-2](figures/focus-2.gif)
922
923上述示例包含以下3步:
924
925- 点击FocusController.requestFocus按钮,第一个Button获焦。
926- 点击focusControl.requestFocus按钮,第二个Button获焦。
927- 点击clearFocus按钮,第二个Button失焦。
928
929## 自定义组件走焦顺序
930
931### nextFocus自定义走焦
932
933```ts
934nextFocus(nextStep: Optional<FocusMovement>): T
935```
936
937若存在配置了nextFocus的组件,则走焦只会按照设置的nextFocus走焦顺序走焦,没有设置自定义走焦或者设置自定义走焦的组件或容器不存在时,仍进行默认走焦规则。
938
939>  **说明:**
940>
941>  - 该能力从API version 18开始支持。
942
943```ts
944@Entry
945@Component
946struct NextFocusExample {
947  build() {
948    Column({space: 30}) {
949      Row().height('30%')
950      Row({space: 10}) {
951        Button('A')
952          .id('A')
953          .nextFocus({forward: 'F', backward: 'C', down: 'B'})
954        Button('B')
955          .id('B')
956          .nextFocus({ down: 'C'})
957        Button('C')
958          .id('C')
959      }
960      Column({space: 10}) {
961        Button('D')
962          .id('D')
963        Button('E')
964          .id('E')
965          .nextFocus({forward: 'A', backward: 'M', up: 'E', right: 'F'})
966      }
967      Row({space: 10}) {
968        Button('F')
969          .id('F')
970          .nextFocus({forward: 'B', down: 'A'});
971      }
972    }.width('100%')
973  }
974}
975```
976Tab键走焦:未配置nextFocus时,Tab键走焦顺序为A->B->C->D->E->F。配置nextFocus之后,Tab键走焦顺序为A->F->B->C->D->E->A。
977
978![NextFocus_Focus_1.gif](figures/NextFocus_Focus_1.gif)
979
980方向键走焦(以方向下键为例):未配置nextFocus时,按下Tab键激活焦点态之后,按方向下键走焦顺序为A->D->E->F。配置nextFocus之后,按下Tab键激活焦点态之后,按方向下键走焦顺序为A->B->C->D->E->F->A。
981
982![NextFocus_Focus_2.gif](figures/NextFocus_Focus_2.gif)
983
984### tabIndex自定义走焦
985
986```ts
987tabIndex(index: number)
988```
989
990tabIndex自定义组件Tab键走焦顺序。
991
992若存在配置了tabIndex大于0的组件,则Tab键走焦只会在tabIndex大于0的组件内,按照tabIndex的值从小到大并循环依次走焦。若没有配置tabIndex大于0的组件,则tabIndex等于0的组件按照组件预设的走焦规则走焦。
993
994> **说明:**
995>
996> 不能同时设置tabIndex与focusScopeId属性。
997>
998> 不建议在[层级页面](#基础概念)中通过单独设置组件的tabIndex属性为负数来控制获焦能力,可以使用focusable属性代替。
999>
1000> tabIndex只能够自定义Tab键走焦,若想同时自定义方向键等走焦能力,建议使用[nextfocus](#nextfocus自定义走焦)。
1001
1002```ts
1003@Entry
1004@Component
1005struct TabIndexExample {
1006  build() {
1007    Column() {
1008      Button('Button1')
1009        .width(140)
1010        .height(45)
1011        .margin(5)
1012      Button('Focus Button1')
1013        .width(140)
1014        .height(45)
1015        .margin(5).tabIndex(1)
1016      Button('Button2')
1017        .width(140)
1018        .height(45)
1019        .margin(5)
1020      Button('Focus Button2')
1021        .width(140)
1022        .height(45)
1023        .margin(5).tabIndex(2)
1024    }.width('100%')
1025  }
1026}
1027```
1028
1029Tab键走焦:只在配置TabIndex的节点间循环走焦。
1030
1031![TabIndex_Focus_1.gif](figures/TabIndex_Focus_1.gif)
1032
1033tabIndex配置在容器上时,如果容器中的所有组件都没有获焦过,则走到第一个可获焦组件上,否则会走到上次获焦的节点。
1034
1035```ts
1036@Entry
1037@Component
1038struct TabIndexExample2 {
1039  build() {
1040    Column() {
1041      Button('Button1')
1042        .width(140)
1043        .height(45)
1044        .margin(5).tabIndex(1)
1045      Column() {
1046        Button('Button2')
1047          .width(140)
1048          .height(45)
1049          .margin(5)
1050        Button('Button3')
1051          .width(140)
1052          .height(45)
1053          .margin(5)
1054      }.tabIndex(2)
1055    }.width('100%')
1056  }
1057}
1058```
1059
1060Tab键走焦:tabIndex配置在容器上。
1061
1062![TabIndex_Focus_2.gif](figures/TabIndex_Focus_2.gif)
1063
1064上述示例包含以下3步:
1065
1066- 使用Tab走焦,焦点在Button1和Button2之间循环走焦(tabIndex配置在Button2和Button3的父组件上)。
1067- 在走焦至Button2时,使用方向下键,将焦点转移至Button3上。
1068- 使用Tab走焦,焦点在Button1和Button3之间循环走焦。
1069
1070## 焦点组与获焦优先级
1071
1072```ts
1073focusScopePriority(scopeId: string, priority?: FocusPriority)
1074```
1075
1076设置当前组件在指定容器内获焦的优先级。需要配合focusScopeId一起使用。
1077
1078
1079```ts
1080focusScopeId(id: string, isGroup?: boolean)
1081```
1082
1083设置当前容器组件的id标识,设置当前容器组件是否为焦点组。焦点组与tabIndex不能混用。
1084
1085```ts
1086// focusTest.ets
1087@Entry
1088@Component
1089struct FocusableExample {
1090  @State inputValue: string = ''
1091
1092  build() {
1093    Scroll() {
1094      Row({ space: 20 }) {
1095        Column({ space: 20 }) {  // 标记为Column1
1096          Column({ space: 5 }) {
1097            Button('Group1')
1098              .width(165)
1099              .height(40)
1100              .fontColor(Color.White)
1101            Row({ space: 5 }) {
1102              Button()
1103                .width(80)
1104                .height(40)
1105                .fontColor(Color.White)
1106              Button()
1107                .width(80)
1108                .height(40)
1109                .fontColor(Color.White)
1110            }
1111            Row({ space: 5 }) {
1112              Button()
1113                .width(80)
1114                .height(40)
1115                .fontColor(Color.White)
1116              Button()
1117                .width(80)
1118                .height(40)
1119                .fontColor(Color.White)
1120            }
1121          }.borderWidth(2).borderColor(Color.Red).borderStyle(BorderStyle.Dashed)
1122          Column({ space: 5 }) {
1123            Button('Group2')
1124              .width(165)
1125              .height(40)
1126              .fontColor(Color.White)
1127            Row({ space: 5 }) {
1128              Button()
1129                .width(80)
1130                .height(40)
1131                .fontColor(Color.White)
1132              Button()
1133                .width(80)
1134                .height(40)
1135                .fontColor(Color.White)
1136                .focusScopePriority('ColumnScope1', FocusPriority.PRIOR)  // Column1首次获焦时获焦
1137            }
1138            Row({ space: 5 }) {
1139              Button()
1140                .width(80)
1141                .height(40)
1142                .fontColor(Color.White)
1143              Button()
1144                .width(80)
1145                .height(40)
1146                .fontColor(Color.White)
1147            }
1148          }.borderWidth(2).borderColor(Color.Green).borderStyle(BorderStyle.Dashed)
1149        }
1150        .focusScopeId('ColumnScope1')
1151        Column({ space: 5 }) {  // 标记为Column2
1152          TextInput({placeholder: 'input', text: this.inputValue})
1153            .onChange((value: string) => {
1154              this.inputValue = value
1155            })
1156            .width(156)
1157          Button('Group3')
1158            .width(165)
1159            .height(40)
1160            .fontColor(Color.White)
1161          Row({ space: 5 }) {
1162            Button()
1163              .width(80)
1164              .height(40)
1165              .fontColor(Color.White)
1166            Button()
1167              .width(80)
1168              .height(40)
1169              .fontColor(Color.White)
1170          }
1171          Button()
1172            .width(165)
1173            .height(40)
1174            .fontColor(Color.White)
1175            .focusScopePriority('ColumnScope2', FocusPriority.PREVIOUS)  // Column2获焦时获焦
1176          Row({ space: 5 }) {
1177            Button()
1178              .width(80)
1179              .height(40)
1180              .fontColor(Color.White)
1181            Button()
1182              .width(80)
1183              .height(40)
1184              .fontColor(Color.White)
1185          }
1186          Button()
1187            .width(165)
1188            .height(40)
1189            .fontColor(Color.White)
1190          Row({ space: 5 }) {
1191            Button()
1192              .width(80)
1193              .height(40)
1194              .fontColor(Color.White)
1195            Button()
1196              .width(80)
1197              .height(40)
1198              .fontColor(Color.White)
1199          }
1200        }.borderWidth(2).borderColor(Color.Orange).borderStyle(BorderStyle.Dashed)
1201        .focusScopeId('ColumnScope2', true)  // Column2为焦点组
1202      }.alignItems(VerticalAlign.Top)
1203    }
1204  }
1205}
1206```
1207
1208
1209![focus-3](figures/focus-3.gif)
1210
1211
1212
1213上述示例包含以下2步:
1214
1215- input方框内设置了焦点组,因此按下Tab键后焦点会快速从input中走出去,而按下方向键后可以在input内走焦。
1216- 左上角的Column没有设置焦点组,因此只能通过Tab键一个一个地走焦。
1217
1218
1219在API version 14,焦点组新增参数arrowStepOut,用于设置能否使用方向键走焦出当前焦点组。
1220```ts
1221focusScopeId(id: string, isGroup?: boolean, arrowStepOut?: boolean)
1222```
1223
1224```ts
1225@Entry
1226@Component
1227struct FocusScopeIdExample {
1228  build() {
1229    Column({ space: 20 }) {
1230      Column() {
1231        Button('Group1')
1232          .width(165)
1233          .height(40)
1234          .margin(5)
1235          .fontColor(Color.White)
1236        Row({ space: 5 }) {
1237          Button("Button1")
1238            .width(80)
1239            .height(40)
1240            .margin(5)
1241            .fontColor(Color.White)
1242          Button("Button2")
1243            .width(80)
1244            .height(40)
1245            .margin(5)
1246            .fontColor(Color.White)
1247        }
1248      }.focusScopeId("1", true, true)
1249      .borderWidth(2).borderColor(Color.Red).borderStyle(BorderStyle.Dashed)
1250
1251      TextInput()
1252      Column() {
1253        Button('Group2')
1254          .width(165)
1255          .height(40)
1256          .margin(5)
1257          .fontColor(Color.White)
1258        Row({ space: 5 }) {
1259          Button("Button3")
1260            .width(80)
1261            .height(40)
1262            .margin(5)
1263            .fontColor(Color.White)
1264          Button("Button4")
1265            .width(80)
1266            .height(40)
1267            .margin(5)
1268            .fontColor(Color.White)
1269        }
1270      }.focusScopeId("2", true, false)
1271      .borderWidth(2).borderColor(Color.Green).borderStyle(BorderStyle.Dashed)
1272
1273      TextInput()
1274    }.width('100%')
1275  }
1276}
1277```
1278
1279
1280![FocusScopeId_1](figures/FocusScopeId_1.gif)
1281
1282上述示例包含以下3步:
1283- Group1和Group2设置焦点组,因此按下Tab键后焦点会快速从Group1和Group2的方框内走出。
1284- Group1设置焦点组时,允许使用方向键走焦出当前焦点组。在Group1方框内走焦时,使用方向键可以走焦至input输入框。
1285- Group2设置焦点组时,不允许使用方向键走焦出当前焦点组。在Group2方框内走焦时,使用方向键无法走焦至input输入框。
1286
1287>  **说明:**
1288>
1289> TextInput组件本身对方向键存在独有处理,因此无法使用方向键直接走出TextInput组件。
1290
1291## 焦点与按键事件
1292
1293当组件获焦且存在点击事件(`onClick`)或单指单击事件(`TapGesture`)时,回车和空格会触发对应的事件回调。
1294
1295>  **说明:**
1296>
1297>  1. 点击事件(`onClick`)或单指单击事件(`TapGesture`)在回车、空格触发对应事件回调时,默认不冒泡传递,即父组件对应[按键事件](../reference/apis-arkui/arkui-ts/ts-universal-events-key.md)不会被同步触发。
1298>  2. 按键事件(`onKeyEvent`)默认冒泡传递,即同时会触发父组件的按键事件回调。
1299>  3. 组件同时存在点击事件(`onClick`)和按键事件(`onKeyEvent`),在回车、空格触发时,两者都会响应。
1300>  4. 获焦组件响应点击事件(`onClick`),与焦点激活态无关。
1301
1302```ts
1303@Entry
1304@Component
1305struct FocusOnclickExample {
1306  @State count: number = 0
1307  @State name: string = 'Button'
1308
1309  build() {
1310    Column() {
1311      Button(this.name)
1312        .fontSize(30)
1313        .onClick(() => {
1314          this.count++
1315          if (this.count % 2 === 0) {
1316            this.name = "count is even number"
1317          } else {
1318            this.name = "count is odd number"
1319          }
1320        }).height(60)
1321    }.height('100%').width('100%').justifyContent(FlexAlign.Center)
1322  }
1323}
1324```
1325![focus-4](figures/focus-4.gif)
1326
1327## 组件获焦能力说明
1328
1329
1330  **表1** 基础组件获焦能力
1331
1332| 基础组件                                     | 是否有获焦能力 | focusable默认值 |
1333| ---------------------------------------- | ------- | ------------ |
1334| [AlphabetIndexer](../reference/apis-arkui/arkui-ts/ts-container-alphabet-indexer.md) | 是       | true         |
1335| [Blank](../reference/apis-arkui/arkui-ts/ts-basic-components-blank.md) | 否       | false        |
1336| [Button](../reference/apis-arkui/arkui-ts/ts-basic-components-button.md) | 是       | true         |
1337| [CalendarPicker](../reference/apis-arkui/arkui-ts/ts-basic-components-calendarpicker.md) | 是       | true         |
1338| [Checkbox](../reference/apis-arkui/arkui-ts/ts-basic-components-checkbox.md) | 是       | true         |
1339| [CheckboxGroup](../reference/apis-arkui/arkui-ts/ts-basic-components-checkboxgroup.md) | 是       | true         |
1340| [ContainerSpan](../reference/apis-arkui/arkui-ts/ts-basic-components-containerspan.md) | 否       | false         |
1341| [DataPanel](../reference/apis-arkui/arkui-ts/ts-basic-components-datapanel.md) | 是       | false        |
1342| [DatePicker](../reference/apis-arkui/arkui-ts/ts-basic-components-datepicker.md) | 是       | true         |
1343| [Divider](../reference/apis-arkui/arkui-ts/ts-basic-components-divider.md) | 是       | false        |
1344| [Gauge](../reference/apis-arkui/arkui-ts/ts-basic-components-gauge.md) | 是       | false        |
1345| [Image](../reference/apis-arkui/arkui-ts/ts-basic-components-image.md) | 是       | false        |
1346| [ImageAnimator](../reference/apis-arkui/arkui-ts/ts-basic-components-imageanimator.md) | 否       | false        |
1347| [ImageSpan](../reference/apis-arkui/arkui-ts/ts-basic-components-imagespan.md)                 | 否       | false        |
1348| [LoadingProgress](../reference/apis-arkui/arkui-ts/ts-basic-components-loadingprogress.md) | 是       | true        |
1349| [Marquee](../reference/apis-arkui/arkui-ts/ts-basic-components-marquee.md) | 否       | false        |
1350| [Menu](../reference/apis-arkui/arkui-ts/ts-basic-components-menu.md) | 是       | true         |
1351| [MenuItem](../reference/apis-arkui/arkui-ts/ts-basic-components-menuitem.md) | 是       | true         |
1352| [MenuItemGroup](../reference/apis-arkui/arkui-ts/ts-basic-components-menuitemgroup.md) | 否       | false         |
1353| [Navigation](../reference/apis-arkui/arkui-ts/ts-basic-components-navigation.md) | 是       | true       |
1354| [NavRouter](../reference/apis-arkui/arkui-ts/ts-basic-components-navrouter.md) | 否       | false        |
1355| [NavDestination](../reference/apis-arkui/arkui-ts/ts-basic-components-navdestination.md) | 是       | true        |
1356| [PatternLock](../reference/apis-arkui/arkui-ts/ts-basic-components-patternlock.md) | 是       | true        |
1357| [Progress](../reference/apis-arkui/arkui-ts/ts-basic-components-progress.md) | 是       | true        |
1358| [QRCode](../reference/apis-arkui/arkui-ts/ts-basic-components-qrcode.md) | 是       | true        |
1359| [Radio](../reference/apis-arkui/arkui-ts/ts-basic-components-radio.md) | 是       | true         |
1360| [Rating](../reference/apis-arkui/arkui-ts/ts-basic-components-rating.md) | 是       | true         |
1361| [RichEditor](../reference/apis-arkui/arkui-ts/ts-basic-components-richeditor.md) | 是       | true         |
1362| [RichText](../reference/apis-arkui/arkui-ts/ts-basic-components-richtext.md) | 否       | false        |
1363| [ScrollBar](../reference/apis-arkui/arkui-ts/ts-basic-components-scrollbar.md) | 否       | false        |
1364| [Search](../reference/apis-arkui/arkui-ts/ts-basic-components-search.md) | 是       | true         |
1365| [Select](../reference/apis-arkui/arkui-ts/ts-basic-components-select.md) | 是       | true         |
1366| [Slider](../reference/apis-arkui/arkui-ts/ts-basic-components-slider.md) | 是       | true         |
1367| [Span](../reference/apis-arkui/arkui-ts/ts-basic-components-span.md) | 否       | false        |
1368| [Stepper](../reference/apis-arkui/arkui-ts/ts-basic-components-stepper.md) | 是       | true         |
1369| [StepperItem](../reference/apis-arkui/arkui-ts/ts-basic-components-stepperitem.md) | 是       | true         |
1370| [SymbolSpan](../reference/apis-arkui/arkui-ts/ts-basic-components-symbolSpan.md) | 否       | false         |
1371| [SymbolGlyph](../reference/apis-arkui/arkui-ts/ts-basic-components-symbolGlyph.md) | 否       | false         |
1372| [Text](../reference/apis-arkui/arkui-ts/ts-basic-components-text.md) | 是       | false        |
1373| [TextArea](../reference/apis-arkui/arkui-ts/ts-basic-components-textarea.md) | 否       | false         |
1374| [TextClock](../reference/apis-arkui/arkui-ts/ts-basic-components-textclock.md) | 否       | false        |
1375| [TextInput](../reference/apis-arkui/arkui-ts/ts-basic-components-textinput.md) | 是       | true         |
1376| [TextPicker](../reference/apis-arkui/arkui-ts/ts-basic-components-textpicker.md) | 是       | true         |
1377| [TextTimer](../reference/apis-arkui/arkui-ts/ts-basic-components-texttimer.md) | 否       | false        |
1378| [TimePicker](../reference/apis-arkui/arkui-ts/ts-basic-components-timepicker.md) | 否       | false         |
1379| [Toggle](../reference/apis-arkui/arkui-ts/ts-basic-components-toggle.md) | 是       | true         |
1380| [XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md) | 是       | false        |
1381
1382  **表2** 容器组件获焦能力
1383
1384| 容器组件                                     | 是否可获焦 | focusable默认值 |
1385| ---------------------------------------- | ----- | ------------ |
1386| [Badge](../reference/apis-arkui/arkui-ts/ts-container-badge.md) | 否     | false        |
1387| [Column](../reference/apis-arkui/arkui-ts/ts-container-column.md) | 是     | true         |
1388| [ColumnSplit](../reference/apis-arkui/arkui-ts/ts-container-columnsplit.md) | 是     | true         |
1389| [Counter](../reference/apis-arkui/arkui-ts/ts-container-counter.md) | 是     | false         |
1390| [EmbeddedComponent](../reference/apis-arkui/arkui-ts/ts-container-embedded-component.md)    | 否     | false         |
1391| [Flex](../reference/apis-arkui/arkui-ts/ts-container-flex.md) | 是     | true         |
1392| [FlowItem](../reference/apis-arkui/arkui-ts/ts-container-flowitem.md)             | 是     | true         |
1393| [FolderStack](../reference/apis-arkui/arkui-ts/ts-container-folderstack.md)             | 是     | true         |
1394| [FormLink](../reference/apis-arkui/arkui-ts/ts-container-formlink.md)               | 否     | false         |
1395| [GridCol](../reference/apis-arkui/arkui-ts/ts-container-gridcol.md) | 是     | true         |
1396| [GridRow](../reference/apis-arkui/arkui-ts/ts-container-gridrow.md) | 是     | true         |
1397| [Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md) | 是     | true         |
1398| [GridItem](../reference/apis-arkui/arkui-ts/ts-container-griditem.md) | 是     | true         |
1399| [Hyperlink](../reference/apis-arkui/arkui-ts/ts-container-hyperlink.md)         | 是     | true         |
1400| [List](../reference/apis-arkui/arkui-ts/ts-container-list.md) | 是     | true         |
1401| [ListItem](../reference/apis-arkui/arkui-ts/ts-container-listitem.md) | 是     | true         |
1402| [ListItemGroup](../reference/apis-arkui/arkui-ts/ts-container-listitemgroup.md) | 是     | true         |
1403| [Navigator](../reference/apis-arkui/arkui-ts/ts-container-navigator.md) | 是     | true         |
1404| [Refresh](../reference/apis-arkui/arkui-ts/ts-container-refresh.md) | 是     | true        |
1405| [RelativeContainer](../reference/apis-arkui/arkui-ts/ts-container-relativecontainer.md) | 否     | false         |
1406| [Row](../reference/apis-arkui/arkui-ts/ts-container-row.md) | 是    | true         |
1407| [RowSplit](../reference/apis-arkui/arkui-ts/ts-container-rowsplit.md) | 是     | true         |
1408| [Scroll](../reference/apis-arkui/arkui-ts/ts-container-scroll.md) | 是     | true         |
1409| [SideBarContainer](../reference/apis-arkui/arkui-ts/ts-container-sidebarcontainer.md) | 是     | true         |
1410| [Stack](../reference/apis-arkui/arkui-ts/ts-container-stack.md) | 是     | true         |
1411| [Swiper](../reference/apis-arkui/arkui-ts/ts-container-swiper.md) | 是     | true         |
1412| [Tabs](../reference/apis-arkui/arkui-ts/ts-container-tabs.md) | 是     | true         |
1413| [TabContent](../reference/apis-arkui/arkui-ts/ts-container-tabcontent.md) | 是     | true         |
1414| [WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md)         | 否     | false         |
1415| [WithTheme](../reference/apis-arkui/arkui-ts/ts-container-with-theme.md)         | 是     | true         |
1416
1417  **表3** 媒体组件获焦能力
1418
1419| 媒体组件                                     | 是否可获焦 | focusable默认值 |
1420| ---------------------------------------- | ----- | ------------ |
1421| [Video](../reference/apis-arkui/arkui-ts/ts-media-components-video.md) | 是     | true         |