• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 焦点事件
2
3
4## 基本概念
5
6- 焦点
7
8  指向当前应用界面上唯一的一个可交互元素,当用户使用键盘、电视遥控器、车机摇杆/旋钮等非指向性输入设备与应用程序进行间接交互时,基于焦点的导航和交互是重要的输入手段。
9
10- 默认焦点
11
12  应用打开或切换页面后,若当前页上存在可获焦的组件,则树形结构的组件树中第一个可获焦的组件默认获得焦点。可以使用[自定义默认焦点](#自定义默认焦点)进行自定义指定。
13
14- 获焦
15
16  指组件获得了焦点,同一时刻,应用中最多只有1个末端组件是获焦的,且此时它的所有祖宗组件(整个组件链)均是获焦的。当期望某个组件获焦,须确保该组件及其所有的祖宗节点均是可获焦的([focusable](#设置组件是否获焦)属性为true)。
17
18- 失焦
19
20  指组件从获焦状态变成了非获焦状态,失去了焦点。组件失焦时,它的所有祖宗组件(失焦组件链)与新的获焦组件链不相同的节点都会失焦。
21
22- 走焦
23
24  表示焦点在当前应用中转移的过程,走焦会带来原焦点组件的失焦和新焦点组件的获焦。应用中焦点发生变化的方式按行为可分为两类:
25
26  - 主动走焦:指开发者/用户主观的行为导致焦点移动,包含:外接键盘上按下TAB/方向键、使用[requestFocus](#focuscontrolrequestfocus)主动给指定组件申请焦点、组件[focusOnTouch](#focusontouch)属性为true后点击组件。
27  - 被动走焦:指组件焦点因其他操作被动的转移焦点,此特性为焦点系统默认行为,无法由开发者自由设定,例如当使用if-else语句将处于获焦的组件删除/将处于获焦的组件(或其父组件)置成不可获焦时、当页面切换时。
28
29- 焦点态
30
31  获焦组件的样式,不同组件的焦点态样式大同小异,默认情况下焦点态不显示,仅使用外接键盘按下TAB键/方向键时才会触发焦点态样式出现。首次触发焦点态显示的TAB键/方向键不会触发走焦。当应用接收到点击事件时(包括手指触屏的按下事件和鼠标左键的按下事件),自动隐藏焦点态样式。焦点态样式由后端组件定义,开发者无法修改。
32
33
34## 走焦规则
35
36走焦规则是指用户使用“TAB键/SHIFT+TAB键/方向键”主动进行走焦,或焦点系统在执行被动走焦时的顺序规则。组件的走焦规则默认由走焦系统定义,由焦点所在的容器决定。
37
38- 线性走焦:常见的容器有Flex、Row、Column、List,这些都是典型的单方向容器,组件在这些容器内的排列都是线性的,那么走焦规则也是线性的。走焦的方向和方向键的方向一致。
39
40  **图1** 线性走焦示意图  
41
42  ![zh-cn_image_0000001562700537](figures/zh-cn_image_0000001562700537.png)
43
44  例如Row容器,使用方向键左右(←/→)即可将焦点在相邻的2个可获焦组件之间来回切换。
45
46- 十字走焦:使用方向键上(↑)下(↓)左(←)右(→)可以使焦点在相邻的组件上切换。典型的是Grid容器,如下图:
47
48  **图2** Grid组件十字走焦示意图  
49
50  ![zh-cn_image_0000001511740580](figures/zh-cn_image_0000001511740580.png)
51
52  >**说明:**
53  >
54  > - TAB/SHIFT+TAB键在以上两种走焦规则上的功能和方向键一致。TAB键等同于“先执行方向键右,若无法走焦,再执行方向键下”,SHIFT+TAB键等同于“先执行方向键左,若无法走焦,再执行方向键上”。
55  >
56  > - 触发走焦的按键是按下的事件(DOWN事件)。
57  >
58  > - 删除组件、设置组件无法获焦后,会使用线性走焦规则,自动先往被删除/Unfocusable组件的前置兄弟组件上走焦,无法走焦的话,再忘后置兄弟组件上走焦。
59
60- tabIndex走焦:给组件设置[tabIndex](../reference/arkui-ts/ts-universal-attributes-focus.md)通用属性,自定义组件的TAB键/SHIFT+TAB键的走焦顺序。
61
62- 区域走焦:给容器组件设置tabIndex通用属性,再结合[groupDefaultFocus](#groupdefaultfocus)通用属性,自定义容器区域的TAB键/SHIFT+TAB键的走焦顺序和默认获焦组件。
63
64- 走焦至容器组件规则:当焦点走焦到容器(该容器没有配置groupDefaultFocus)上时,若该容器组件为首次获焦,则会先计算目标容器组件的子组件的区域位置,得到距离目标容器中心点最近的子组件,焦点会走到目标容器上的该子组件上。若该容器非首次获焦,焦点会自动走焦到上一次目标容器中获焦的子组件。
65
66- 焦点交互:当某组件获焦时,该组件的固有点击任务或开发者绑定的onClick回调任务,会自动挂载到空格/回车按键上,当按下按键时,任务就和手指/鼠标点击一样被执行。
67
68
69>**说明:**
70>
71>本文涉及到的焦点均为组件焦点,另外一个焦点的概念是:窗口焦点,指向当前获焦的窗口。当窗口失焦时,该窗口应用中的所有获焦组件全部失焦。
72
73
74## 监听组件的焦点变化
75
76
77```ts
78onFocus(event: () => void)
79```
80
81
82获焦事件回调,绑定该API的组件获焦时,回调响应。
83
84
85
86```ts
87onBlur(event:() => void)
88```
89
90
91失焦事件回调,绑定该API的组件失焦时,回调响应。
92
93
94onFocus和onBlur两个接口通常成对使用,来监听组件的焦点变化。
95
96
97以下示例代码展示获焦/失焦回调的使用方法:
98
99
100
101```ts
102// xxx.ets
103@Entry
104@Component
105struct FocusEventExample {
106  @State oneButtonColor: Color = Color.Gray;
107  @State twoButtonColor: Color = Color.Gray;
108  @State threeButtonColor: Color = Color.Gray;
109
110  build() {
111    Column({ space: 20 }) {
112      // 通过外接键盘的上下键可以让焦点在三个按钮间移动,按钮获焦时颜色变化,失焦时变回原背景色
113      Button('First Button')
114        .width(260)
115        .height(70)
116        .backgroundColor(this.oneButtonColor)
117        .fontColor(Color.Black)
118          // 监听第一个组件的获焦事件,获焦后改变颜色
119        .onFocus(() => {
120          this.oneButtonColor = Color.Green;
121        })
122          // 监听第一个组件的失焦事件,失焦后改变颜色
123        .onBlur(() => {
124          this.oneButtonColor = Color.Gray;
125        })
126
127      Button('Second Button')
128        .width(260)
129        .height(70)
130        .backgroundColor(this.twoButtonColor)
131        .fontColor(Color.Black)
132          // 监听第二个组件的获焦事件,获焦后改变颜色
133        .onFocus(() => {
134          this.twoButtonColor = Color.Green;
135        })
136          // 监听第二个组件的失焦事件,失焦后改变颜色
137        .onBlur(() => {
138          this.twoButtonColor = Color.Grey;
139        })
140
141      Button('Third Button')
142        .width(260)
143        .height(70)
144        .backgroundColor(this.threeButtonColor)
145        .fontColor(Color.Black)
146          // 监听第三个组件的获焦事件,获焦后改变颜色
147        .onFocus(() => {
148          this.threeButtonColor = Color.Green;
149        })
150          // 监听第三个组件的失焦事件,失焦后改变颜色
151        .onBlur(() => {
152          this.threeButtonColor = Color.Gray ;
153        })
154    }.width('100%').margin({ top: 20 })
155  }
156}
157```
158
159
160![zh-cn_image_0000001511740584](figures/zh-cn_image_0000001511740584.gif)
161
162
163上述示例包含以下4步:
164
165
1661. 应用打开时,“First Button”默认获取焦点,onFocus回调响应,背景色变成绿色。
167
1682. 按下TAB键(或方向键下↓),“First Button”显示焦点态样式:组件外围有一个蓝色的闭合框。不触发走焦,焦点仍然在“First Button”上。
169
1703. 按下TAB键(或方向键下↓),触发走焦,“Second Button”获焦,onFocus回调响应,背景色变成绿色;“First Button”失焦、onBlur回调响应,背景色变回灰色。
171
1724. 按下TAB键(或方向键下↓),触发走焦,“Third Button”获焦,onFocus回调响应,背景色变成绿色;“Second Button”失焦、onBlur回调响应,背景色变回灰色。
173
174
175## 设置组件是否获焦
176
177通过focusable接口设置组件是否可获焦:
178
179
180```ts
181focusable(value: boolean)
182```
183
184按照组件的获焦能力可大致分为三类:
185
186- 默认可获焦的组件,通常是有交互行为的组件,例如Button、Checkbox,TextInput组件,此类组件无需设置任何属性,默认即可获焦。
187
188- 有获焦能力,但默认不可获焦的组件,典型的是Text、Image组件,此类组件缺省情况下无法获焦,若需要使其获焦,可使用通用属性focusable(true)使能。
189
190- 无获焦能力的组件,通常是无任何交互行为的展示类组件,例如Blank、Circle组件,此类组件即使使用focusable属性也无法使其可获焦。
191
192
193>**说明:**
194> - focusable为false表示组件不可获焦,同样可以使组件变成不可获焦的还有通用属性[enabled](../reference/arkui-ts/ts-universal-attributes-enable.md)。
195>
196> - 当某组件处于获焦状态时,将其的focusable属性或enabled属性设置为false,会自动使该组件失焦,然后焦点按照[走焦规则](#走焦规则)将焦点转移给其他组件。
197
198  **表1** 基础组件获焦能力
199
200| 基础组件                                     | 是否有获焦能力 | focusable默认值 | 走焦规则     |
201| ---------------------------------------- | ------- | ------------ | -------- |
202| [AlphabetIndexer](../reference/arkui-ts/ts-container-alphabet-indexer.md) | 是       | true         | 线性走焦     |
203| [Blank](../reference/arkui-ts/ts-basic-components-blank.md) | 否       | false        | /        |
204| [Button](../reference/arkui-ts/ts-basic-components-button.md) | 是       | true         | /        |
205| [Checkbox](../reference/arkui-ts/ts-basic-components-checkbox.md) | 是       | true         | /        |
206| [CheckboxGroup](../reference/arkui-ts/ts-basic-components-checkboxgroup.md) | 是       | true         | /        |
207| [DataPanel](../reference/arkui-ts/ts-basic-components-datapanel.md) | 否       | false        | /        |
208| [DatePicker](../reference/arkui-ts/ts-basic-components-datepicker.md) | 是       | true         | 线性走焦     |
209| [Divider](../reference/arkui-ts/ts-basic-components-divider.md) | 否       | false        | /        |
210| [Formcomponent](../reference/arkui-ts/ts-basic-components-formcomponent.md) | 否       | false        | /        |
211| [Gauge](../reference/arkui-ts/ts-basic-components-gauge.md) | 否       | false        | /        |
212| [Image](../reference/arkui-ts/ts-basic-components-image.md) | 是       | false        | /        |
213| [ImageAnimator](../reference/arkui-ts/ts-basic-components-imageanimator.md) | 是       | false        | /        |
214| [LoadingProgress](../reference/arkui-ts/ts-basic-components-loadingprogress.md) | 否       | false        | /        |
215| [Marquee](../reference/arkui-ts/ts-basic-components-marquee.md) | 否       | false        | /        |
216| [Menu](../reference/arkui-ts/ts-basic-components-menu.md) | 是       | true         | 线性走焦     |
217| [MenuItem](../reference/arkui-ts/ts-basic-components-menuitem.md) | 是       | true         | /        |
218| [MenuItemGroup](../reference/arkui-ts/ts-basic-components-menuitemgroup.md) | 是       | true         | 线性走焦     |
219| [Navigation](../reference/arkui-ts/ts-basic-components-navigation.md) | 否       | false        | 组件自定义    |
220| [NavRouter](../reference/arkui-ts/ts-basic-components-navrouter.md) | 否       | false        | 跟随子容器    |
221| [NavDestination](../reference/arkui-ts/ts-basic-components-navdestination.md) | 否       | false        | 线性走焦     |
222| [PatternLock](../reference/arkui-ts/ts-basic-components-patternlock.md) | 否       | false        | /        |
223| [PluginComponent](../reference/arkui-ts/ts-basic-components-plugincomponent.md) | 否       | false        | /        |
224| [Progress](../reference/arkui-ts/ts-basic-components-progress.md) | 否       | false        | /        |
225| [QRCode](../reference/arkui-ts/ts-basic-components-qrcode.md) | 否       | false        | /        |
226| [Radio](../reference/arkui-ts/ts-basic-components-radio.md) | 是       | true         | /        |
227| [Rating](../reference/arkui-ts/ts-basic-components-rating.md) | 是       | true         | /        |
228| [RemoteWindow](../reference/arkui-ts/ts-basic-components-remotewindow.md) | 否       | false        | /        |
229| [RichText](../reference/arkui-ts/ts-basic-components-richtext.md) | 否       | false        | /        |
230| [ScrollBar](../reference/arkui-ts/ts-basic-components-scrollbar.md) | 否       | false        | /        |
231| [Search](../reference/arkui-ts/ts-basic-components-search.md) | 是       | true         | /        |
232| [Select](../reference/arkui-ts/ts-basic-components-select.md) | 是       | true         | 线性走焦     |
233| [Slider](../reference/arkui-ts/ts-basic-components-slider.md) | 是       | true         | /        |
234| [Span](../reference/arkui-ts/ts-basic-components-span.md) | 否       | false        | /        |
235| [Stepper](../reference/arkui-ts/ts-basic-components-stepper.md) | 是       | true         | /        |
236| [StepperItem](../reference/arkui-ts/ts-basic-components-stepperitem.md) | 是       | true         | /        |
237| [Text](../reference/arkui-ts/ts-basic-components-text.md) | 是       | false        | /        |
238| [TextArea](../reference/arkui-ts/ts-basic-components-textarea.md) | 是       | true         | /        |
239| [TextClock](../reference/arkui-ts/ts-basic-components-textclock.md) | 否       | false        | /        |
240| [TextInput](../reference/arkui-ts/ts-basic-components-textinput.md) | 是       | true         | /        |
241| [TextPicker](../reference/arkui-ts/ts-basic-components-textpicker.md) | 是       | true         | 线性走焦     |
242| [TextTimer](../reference/arkui-ts/ts-basic-components-texttimer.md) | 否       | false        | /        |
243| [TimePicker](../reference/arkui-ts/ts-basic-components-timepicker.md) | 是       | true         | 线性走焦     |
244| [Toggle](../reference/arkui-ts/ts-basic-components-toggle.md) | 是       | true         | /        |
245| [Web](../reference/arkui-ts/ts-basic-components-web.md) | 是       | true         | Web组件自定义 |
246| [XComponent](../reference/arkui-ts/ts-basic-components-xcomponent.md) | 否       | false        | /        |
247
248  **表2** 容器组件获焦能力
249
250| 容器组件                                     | 是否可获焦 | focusable默认值 | 走焦规则     |
251| ---------------------------------------- | ----- | ------------ | -------- |
252| [AbilityComponent](../reference/arkui-ts/ts-container-ability-component.md) | 否     | false        | /        |
253| [Badge](../reference/arkui-ts/ts-container-badge.md) | 否     | false        | /        |
254| [Column](../reference/arkui-ts/ts-container-column.md) | 是     | true         | 线性走焦     |
255| [ColumnSplit](../reference/arkui-ts/ts-container-columnsplit.md) | 是     | true         | /        |
256| [Counter](../reference/arkui-ts/ts-container-counter.md) | 是     | true         | 线性走焦     |
257| [Flex](../reference/arkui-ts/ts-container-flex.md) | 是     | true         | 线性走焦     |
258| [GridCol](../reference/arkui-ts/ts-container-gridcol.md) | 是     | true         | 容器组件自定义  |
259| [GridRow](../reference/arkui-ts/ts-container-gridrow.md) | 是     | true         | 容器组件自定义  |
260| [Grid](../reference/arkui-ts/ts-container-grid.md) | 是     | true         | 容器组件自定义  |
261| [GridItem](../reference/arkui-ts/ts-container-griditem.md) | 是     | true         | 跟随子组件    |
262| [List](../reference/arkui-ts/ts-container-list.md) | 是     | true         | 线性走焦     |
263| [ListItem](../reference/arkui-ts/ts-container-listitem.md) | 是     | true         | 跟随子组件    |
264| [ListItemGroup](../reference/arkui-ts/ts-container-listitemgroup.md) | 是     | true         | 跟随List组件 |
265| [Navigator](../reference/arkui-ts/ts-container-navigator.md) | 否     | true         | 容器组件自定义  |
266| [Panel](../reference/arkui-ts/ts-container-panel.md) | 否     | true         | 跟随子组件    |
267| [Refresh](../reference/arkui-ts/ts-container-refresh.md) | 否     | false        | /        |
268| [RelativeContainer](../reference/arkui-ts/ts-container-relativecontainer.md) | 否     | true         | 容器组件自定义  |
269| [Row](../reference/arkui-ts/ts-container-row.md) | 是     | true         | 线性走焦     |
270| [RowSplit](../reference/arkui-ts/ts-container-rowsplit.md) | 是     | true         | /        |
271| [Scroll](../reference/arkui-ts/ts-container-scroll.md) | 是     | true         | 线性走焦     |
272| [SideBarContainer](../reference/arkui-ts/ts-container-sidebarcontainer.md) | 是     | true         | 线性走焦     |
273| [Stack](../reference/arkui-ts/ts-container-stack.md) | 是     | true         | 线性走焦     |
274| [Swiper](../reference/arkui-ts/ts-container-swiper.md) | 是     | true         | 容器组件自定义  |
275| [Tabs](../reference/arkui-ts/ts-container-tabs.md) | 是     | true         | 容器组件自定义  |
276| [TabContent](../reference/arkui-ts/ts-container-tabcontent.md) | 是     | true         | 跟随子组件    |
277
278  **表3** 媒体组件获焦能力
279
280| 媒体组件                                     | 是否可获焦 | focusable默认值 | 走焦规则 |
281| ---------------------------------------- | ----- | ------------ | ---- |
282| [Video](../reference/arkui-ts/ts-media-components-video.md) | 是     | true         | /    |
283
284  **表4** 画布组件获焦能力
285
286| 画布组件                                     | 是否可获焦 | focusable默认值 | 走焦规则 |
287| ---------------------------------------- | ----- | ------------ | ---- |
288| [Canvas](../reference/arkui-ts/ts-components-canvas-canvas.md) | 否     | false        | /    |
289
290
291以下示例为大家展示focusable接口的使用方法:
292
293
294
295```ts
296// xxx.ets
297@Entry
298@Component
299struct FocusableExample {
300  @State textFocusable: boolean = true;
301  @State color1: Color = Color.Yellow;
302  @State color2: Color = Color.Yellow;
303
304  build() {
305    Column({ space: 5 }) {
306      Text('Default Text')    // 第一个Text组件未设置focusable属性,默认不可获焦
307        .borderColor(this.color1)
308        .borderWidth(2)
309        .width(300)
310        .height(70)
311        .onFocus(() => {
312          this.color1 = Color.Blue;
313        })
314        .onBlur(() => {
315          this.color1 = Color.Yellow;
316        })
317      Divider()
318
319      Text('focusable: ' + this.textFocusable)    // 第二个Text设置了focusable属性,初始值为true
320        .borderColor(this.color2)
321        .borderWidth(2)
322        .width(300)
323        .height(70)
324        .focusable(this.textFocusable)
325        .onFocus(() => {
326          this.color2 = Color.Blue;
327        })
328        .onBlur(() => {
329          this.color2 = Color.Yellow;
330        })
331
332      Divider()
333
334      Row() {
335        Button('Button1')
336          .width(140).height(70)
337        Button('Button2')
338          .width(160).height(70)
339      }
340
341      Divider()
342      Button('Button3')
343        .width(300).height(70)
344
345      Divider()
346    }.width('100%').justifyContent(FlexAlign.Center)
347    .onKeyEvent((e) => {    // 绑定onKeyEvent,在该Column组件获焦时,按下'F'键,可将第二个Text的focusable置反
348      if (e.keyCode === 2022 && e.type === KeyType.Down) {
349        this.textFocusable = !this.textFocusable;
350      }
351    })
352  }
353}
354```
355
356
357运行效果:
358
359
360![zh-cn_image_0000001511900540](figures/zh-cn_image_0000001511900540.gif)
361
362
363上述示例包含默认获焦和主动走焦两部分:
364
365
366**默认获焦:**
367
368
369- 根据默认焦点的说明,该应用打开后,默认第一个可获焦元素获焦:
370
371- 第一个Text组件没有设置focusable(true)属性,该Text组件无法获焦。
372
373- 第二个Text组件的focusable属性显式设置为true,说明该组件可获焦,那么默认焦点将置到它身上。
374
375
376**主动走焦:**
377
378
379按键盘F键,触发onKeyEvent,focusable置为false,Text组件变成不可获焦,焦点自动转移,按照被动走焦中的说明项,焦点会自动从Text组件先向上寻找下一个可获焦组件,由于上一个组件是一个不可获焦的Text,所以向下寻找下一个可获焦的组件,找到并使焦点转移到Row容器上,根据[走焦至容器规则](#走焦规则),计算Button1和Button2的位置,Button2比Button1更大,因此焦点会自动转移到Button2上。
380
381
382## 自定义默认焦点
383
384
385```ts
386defaultFocus(value: boolean)
387```
388
389焦点系统在页面初次构建完成时,会搜索当前页下的所有组件,找到第一个绑定了defaultFocus(true)的组件,然后将该组件置为默认焦点,若无任何组件绑定defaultFocus(true),则将第一个找到的可获焦的组件置为默认焦点。
390
391以如下应用为例,应用布局如下:
392
393![zh-cn_image_0000001563060793](figures/zh-cn_image_0000001563060793.png)
394
395以下是实现该应用的示例代码,且示例代码中没有设置defaultFocus:
396
397
398```ts
399// xxx.ets
400import promptAction from '@ohos.promptAction';
401
402class MyDataSource implements IDataSource {
403  private list: number[] = [];
404  private listener: DataChangeListener;
405
406  constructor(list: number[]) {
407    this.list = list;
408  }
409
410  totalCount(): number {
411    return this.list.length;
412  }
413
414  getData(index: number): any {
415    return this.list[index];
416  }
417
418  registerDataChangeListener(listener: DataChangeListener): void {
419    this.listener = listener;
420  }
421
422  unregisterDataChangeListener() {
423  }
424}
425
426@Entry
427@Component
428struct SwiperExample {
429  private swiperController: SwiperController = new SwiperController()
430  private data: MyDataSource = new MyDataSource([])
431
432  aboutToAppear(): void {
433    let list = []
434    for (let i = 1; i <= 4; i++) {
435      list.push(i.toString());
436    }
437    this.data = new MyDataSource(list);
438  }
439
440  build() {
441    Column({ space: 5 }) {
442      Swiper(this.swiperController) {
443        LazyForEach(this.data, (item: string) => {
444          Row({ space: 20 }) {
445            Column() {
446              Button('1').width(200).height(200)
447                .fontSize(40)
448                .backgroundColor('#dadbd9')
449            }
450
451            Column({ space: 20 }) {
452              Row({ space: 20 }) {
453                Button('2')
454                  .width(100)
455                  .height(100)
456                  .fontSize(40)
457                  .type(ButtonType.Normal)
458                  .borderRadius(20)
459                  .backgroundColor('#dadbd9')
460                Button('3')
461                  .width(100)
462                  .height(100)
463                  .fontSize(40)
464                  .type(ButtonType.Normal)
465                  .borderRadius(20)
466                  .backgroundColor('#dadbd9')
467              }
468
469              Row({ space: 20 }) {
470                Button('4')
471                  .width(100)
472                  .height(100)
473                  .fontSize(40)
474                  .type(ButtonType.Normal)
475                  .borderRadius(20)
476                  .backgroundColor('#dadbd9')
477                Button('5')
478                  .width(100)
479                  .height(100)
480                  .fontSize(40)
481                  .type(ButtonType.Normal)
482                  .borderRadius(20)
483                  .backgroundColor('#dadbd9')
484              }
485
486              Row({ space: 20 }) {
487                Button('6')
488                  .width(100)
489                  .height(100)
490                  .fontSize(40)
491                  .type(ButtonType.Normal)
492                  .borderRadius(20)
493                  .backgroundColor('#dadbd9')
494                Button('7')
495                  .width(100)
496                  .height(100)
497                  .fontSize(40)
498                  .type(ButtonType.Normal)
499                  .borderRadius(20)
500                  .backgroundColor('#dadbd9')
501              }
502            }
503          }
504          .width(480)
505          .height(380)
506          .justifyContent(FlexAlign.Center)
507          .borderWidth(2)
508          .borderColor(Color.Gray)
509          .backgroundColor(Color.White)
510        }, item => item)
511      }
512      .cachedCount(2)
513      .index(0)
514      .interval(4000)
515      .indicator(true)
516      .loop(true)
517      .duration(1000)
518      .itemSpace(0)
519      .curve(Curve.Linear)
520      .onChange((index: number) => {
521        console.info(index.toString());
522      })
523      .margin({ left: 20, top: 20, right: 20 })
524
525      Row({ space: 40 }) {
526        Button('←')
527          .fontSize(40)
528          .fontWeight(FontWeight.Bold)
529          .fontColor(Color.Black)
530          .backgroundColor(Color.Transparent)
531          .onClick(() => {
532            this.swiperController.showPrevious();
533          })
534        Button('→')
535          .fontSize(40)
536          .fontWeight(FontWeight.Bold)
537          .fontColor(Color.Black)
538          .backgroundColor(Color.Transparent)
539          .onClick(() => {
540            this.swiperController.showNext();
541          })
542      }
543      .width(480)
544      .height(50)
545      .justifyContent(FlexAlign.Center)
546      .borderWidth(2)
547      .borderColor(Color.Gray)
548      .backgroundColor('#f7f6dc')
549
550      Row({ space: 40 }) {
551        Button('Cancel')
552          .fontSize(30)
553          .fontColor('#787878')
554          .type(ButtonType.Normal)
555          .width(140)
556          .height(50)
557          .backgroundColor('#dadbd9')
558
559        Button('OK')
560          .fontSize(30)
561          .fontColor('#787878')
562          .type(ButtonType.Normal)
563          .width(140)
564          .height(50)
565          .backgroundColor('#dadbd9')
566          .onClick(() => {
567            promptAction.showToast({ message: 'Button OK on clicked' });
568          })
569      }
570      .width(480)
571      .height(80)
572      .justifyContent(FlexAlign.Center)
573      .borderWidth(2)
574      .borderColor(Color.Gray)
575      .backgroundColor('#dff2e4')
576      .margin({ left: 20, bottom: 20, right: 20 })
577    }.backgroundColor('#f2f2f2')
578    .margin({ left: 50, top: 50, right: 20 })
579  }
580}
581```
582
583
584当前应用上无任何defaultFocus设置,所以第一个可获焦的组件默认获取焦点,按下TAB键/方向键让获焦的组件显示焦点态样式:
585
586
587![zh-cn_image_0000001511421360](figures/zh-cn_image_0000001511421360.gif)
588
589
590假设开发者想让应用打开的时候,无需执行多余的切换焦点操作,直接点击按键的空格/回车键,就可以执行Button-OK的onClick回调操作,那么就可以给这个Button绑定defaultFocus(true),让它成为该页面上的默认焦点:
591
592
593
594```ts
595Button('OK')
596  .defaultFocus(true)    // 设置Button-OK为defaultFocus
597  .fontSize(30)
598  .fontColor('#787878')
599  .type(ButtonType.Normal)
600  .width(140).height(50).backgroundColor('#dadbd9')
601  .onClick(() => {
602    promptAction.showToast({ message: 'Button OK on clicked' });
603  })
604```
605
606
607![zh-cn_image_0000001562940617](figures/zh-cn_image_0000001562940617.gif)
608
609
610打开应用后按TAB键,Button-OK显示了焦点态,说明默认焦点变更到了Button-OK上。然后按下空格,响应了Button-OK的onClick事件。
611
612
613## 自定义TAB键走焦顺序
614
615
616```ts
617tabIndex(index: number)
618```
619
620tabIndex用于设置自定义TAB键走焦顺序,默认值为0。使用“TAB/Shift+TAB键”走焦时(方向键不影响),系统会自动获取到所有配置了tabIndex大于0的组件,然后按照递增/递减排序进行走焦。
621
622
623以[defaultFocus](#自定义默认焦点)提供的示例为例,默认情况下的走焦顺序如下:
624
625
626![zh-cn_image_0000001511421364](figures/zh-cn_image_0000001511421364.gif)
627
628
629默认的走焦顺序从第一个获焦组件一路走到最后一个获焦组件,会经历Button1-&gt;Button4-&gt;Button5-&gt;Button7-&gt;左箭头-&gt;右箭头-&gt;ButtonOK。这种走焦队列比较完整,遍历了大部分的组件。但缺点是从第一个走到最后一个所经历的路径较长。
630
631
632如果想实现快速的从第一个走到最后一个,又不想牺牲太多的遍历完整性,就可以使用tabIndex通用属性。
633
634
635比如:开发者把白色的区域当为一个整体,黄色的区域当为一个整体,绿色的区域当为一个整体,实现Button1-&gt;左箭头-&gt;ButtonOK这种队列的走焦顺序,只需要在Button1、左箭头、ButtonOK这三个组件上依次增加tabIndex(1)、tabIndex(2)、tabIndex(3)。tabIndex的参数表示TAB走焦的顺序(从大于0的数字开始,从小到大排列)。
636
637
638
639```ts
640  Button('1').width(200).height(200)
641    .fontSize(40)
642    .backgroundColor('#dadbd9')
643    .tabIndex(1)    // Button-1设置为第一个tabIndex节点
644```
645
646
647
648```ts
649  Button('←')
650    .fontSize(40)
651    .fontWeight(FontWeight.Bold)
652    .fontColor(Color.Black)
653    .backgroundColor(Color.Transparent)
654    .onClick(() => {
655      this.swiperController.showPrevious();
656    })
657    .tabIndex(2)    // Button-左箭头设置为第二个tabIndex节点
658```
659
660
661
662```ts
663Button('OK')
664  .fontSize(30)
665  .fontColor('#787878')
666  .type(ButtonType.Normal)
667  .width(140).height(50).backgroundColor('#dadbd9')
668  .onClick(() => {
669    promptAction.showToast({ message: 'Button OK on clicked' });
670  })
671  .tabIndex(3)    // Button-OK设置为第三个tabIndex节点
672```
673
674
675![zh-cn_image_0000001511580976](figures/zh-cn_image_0000001511580976.gif)
676
677
678>**说明:**
679> - 当焦点处于tabIndex(大于0)节点上时,TAB/ShiftTAB会优先在tabIndex(大于0)的队列中寻找后置/前置的节点,存在则走焦至相应的tabIndex节点。若不存在,则使用默认的走焦逻辑继续往后/往前走焦。
680>
681> - 当焦点处于tabIndex(等于0)节点上时,TAB/ShiftTAB使用默认的走焦逻辑走焦,走焦的过程中会跳过tabIndex(大于0)和tabIndex(小于0)的节点。
682>
683> - 当焦点处于tabIndex(小于0)节点上时,TAB/ShiftTAB无法走焦。
684
685
686### groupDefaultFocus
687
688
689```ts
690groupDefaultFocus(value: boolean)
691```
692
693[自定义TAB键走焦顺序](#自定义tab键走焦顺序)中所展示的使用tabIndex完成快速走焦的能力有如下问题:
694
695每个区域(白色/黄色/绿色三个区域)都设置了某个组件为tabIndex节点(白色-Button1、黄色-左箭头、绿色-ButtonOK),但这样设置之后,只能在这3个组件上按TAB/ShiftTab键走焦时会有快速走焦的效果。
696
697解决方案是给每个区域的容器设置tabIndex,但是这样设置的问题是:第一次走焦到容器上时,获焦的子组件是默认的第一个可获焦组件,并不是自己想要的组件(Button1、左箭头、ButtonOK)。
698
699这样便引入了groupDefaultFocus通用属性,参数:boolean,默认值:false。
700
701用法需和tabIndex组合使用,使用tabIndex给区域(容器)绑定走焦顺序,然后给Button1、左箭头、ButtonOK绑定groupDefaultFocus(true),这样在首次走焦到目标区域(容器)上时,它的绑定了groupDefaultFocus(true)的子组件同时获得焦点。
702
703
704```ts
705// xxx.ets
706import promptAction from '@ohos.promptAction';
707
708class MyDataSource implements IDataSource {
709  private list: number[] = [];
710  private listener: DataChangeListener;
711
712  constructor(list: number[]) {
713    this.list = list;
714  }
715
716  totalCount(): number {
717    return this.list.length;
718  }
719
720  getData(index: number): any {
721    return this.list[index];
722  }
723
724  registerDataChangeListener(listener: DataChangeListener): void {
725    this.listener = listener;
726  }
727
728  unregisterDataChangeListener() {
729  }
730}
731
732@Entry
733@Component
734struct SwiperExample {
735  private swiperController: SwiperController = new SwiperController()
736  private data: MyDataSource = new MyDataSource([])
737
738  aboutToAppear(): void {
739    let list = []
740    for (let i = 1; i <= 4; i++) {
741      list.push(i.toString());
742    }
743    this.data = new MyDataSource(list);
744  }
745
746  build() {
747    Column({ space: 5 }) {
748      Swiper(this.swiperController) {
749        LazyForEach(this.data, (item: string) => {
750          Row({ space: 20 }) {    // 设置该Row组件为tabIndex的第一个节点
751            Column() {
752              Button('1').width(200).height(200)
753                .fontSize(40)
754                .backgroundColor('#dadbd9')
755                .groupDefaultFocus(true)    // 设置Button-1为第一个tabIndex的默认焦点
756            }
757
758            Column({ space: 20 }) {
759              Row({ space: 20 }) {
760                Button('2')
761                  .width(100)
762                  .height(100)
763                  .fontSize(40)
764                  .type(ButtonType.Normal)
765                  .borderRadius(20)
766                  .backgroundColor('#dadbd9')
767                Button('3')
768                  .width(100)
769                  .height(100)
770                  .fontSize(40)
771                  .type(ButtonType.Normal)
772                  .borderRadius(20)
773                  .backgroundColor('#dadbd9')
774              }
775
776              Row({ space: 20 }) {
777                Button('4')
778                  .width(100)
779                  .height(100)
780                  .fontSize(40)
781                  .type(ButtonType.Normal)
782                  .borderRadius(20)
783                  .backgroundColor('#dadbd9')
784                Button('5')
785                  .width(100)
786                  .height(100)
787                  .fontSize(40)
788                  .type(ButtonType.Normal)
789                  .borderRadius(20)
790                  .backgroundColor('#dadbd9')
791              }
792
793              Row({ space: 20 }) {
794                Button('6')
795                  .width(100)
796                  .height(100)
797                  .fontSize(40)
798                  .type(ButtonType.Normal)
799                  .borderRadius(20)
800                  .backgroundColor('#dadbd9')
801                Button('7')
802                  .width(100)
803                  .height(100)
804                  .fontSize(40)
805                  .type(ButtonType.Normal)
806                  .borderRadius(20)
807                  .backgroundColor('#dadbd9')
808              }
809            }
810          }
811          .width(480)
812          .height(380)
813          .justifyContent(FlexAlign.Center)
814          .borderWidth(2)
815          .borderColor(Color.Gray)
816          .backgroundColor(Color.White)
817          .tabIndex(1)
818        }, item => item)
819      }
820      .cachedCount(2)
821      .index(0)
822      .interval(4000)
823      .indicator(true)
824      .loop(true)
825      .duration(1000)
826      .itemSpace(0)
827      .curve(Curve.Linear)
828      .onChange((index: number) => {
829        console.info(index.toString());
830      })
831      .margin({ left: 20, top: 20, right: 20 })
832
833      Row({ space: 40 }) {    // 设置该Row组件为第二个tabIndex节点
834        Button('←')
835          .fontSize(40)
836          .fontWeight(FontWeight.Bold)
837          .fontColor(Color.Black)
838          .backgroundColor(Color.Transparent)
839          .onClick(() => {
840            this.swiperController.showPrevious();
841          })
842          .groupDefaultFocus(true)    // 设置Button-左箭头为第二个tabIndex节点的默认焦点
843        Button('→')
844          .fontSize(40)
845          .fontWeight(FontWeight.Bold)
846          .fontColor(Color.Black)
847          .backgroundColor(Color.Transparent)
848          .onClick(() => {
849            this.swiperController.showNext();
850          })
851      }
852      .width(480)
853      .height(50)
854      .justifyContent(FlexAlign.Center)
855      .borderWidth(2)
856      .borderColor(Color.Gray)
857      .backgroundColor('#f7f6dc')
858      .tabIndex(2)
859
860      Row({ space: 40 }) {    // 设置该Row组件为第三个tabIndex节点
861        Button('Cancel')
862          .fontSize(30)
863          .fontColor('#787878')
864          .type(ButtonType.Normal)
865          .width(140)
866          .height(50)
867          .backgroundColor('#dadbd9')
868
869        Button('OK')
870          .fontSize(30)
871          .fontColor('#787878')
872          .type(ButtonType.Normal)
873          .width(140)
874          .height(50)
875          .backgroundColor('#dadbd9')
876          .defaultFocus(true)
877          .onClick(() => {
878            promptAction.showToast({ message: 'Button OK on clicked' });
879          })
880          .groupDefaultFocus(true)    // 设置Button-OK为第三个tabIndex节点的默认焦点
881      }
882      .width(480)
883      .height(80)
884      .justifyContent(FlexAlign.Center)
885      .borderWidth(2)
886      .borderColor(Color.Gray)
887      .backgroundColor('#dff2e4')
888      .margin({ left: 20, bottom: 20, right: 20 })
889      .tabIndex(3)
890    }.backgroundColor('#f2f2f2')
891    .margin({ left: 50, top: 50, right: 20 })
892  }
893}
894```
895
896![zh-cn_image_0000001562700533](figures/zh-cn_image_0000001562700533.gif)
897
898
899### focusOnTouch
900
901
902```ts
903focusOnTouch(value: boolean)
904```
905
906点击获焦能力,参数:boolean,默认值:false(输入类组件:TextInput、TextArea、Search、Web默认值是true)。
907
908点击是指使用触屏或鼠标左键进行单击,默认为false的组件,例如Button,不绑定该API时,点击Button不会使其获焦,当给Button绑定focusOnTouch(true)时,点击Button会使Button立即获得焦点。
909
910给容器绑定focusOnTouch(true)时,点击容器区域,会立即使容器的第一个可获焦组件获得焦点。
911
912示例代码:
913
914
915```ts
916// requestFocus.ets
917import promptAction from '@ohos.promptAction';
918
919@Entry
920@Component
921struct RequestFocusExample {
922  @State idList: string[] = ['A', 'B', 'C', 'D', 'E', 'F', 'N']
923
924  build() {
925    Column({ space:20 }){
926      Button("id: " + this.idList[0] + " focusOnTouch(true) + focusable(false)")
927        .width(400).height(70).fontColor(Color.White).focusOnTouch(true)
928        .focusable(false)
929      Button("id: " + this.idList[1] + " default")
930        .width(400).height(70).fontColor(Color.White)
931      Button("id: " + this.idList[2] + " focusOnTouch(false)")
932        .width(400).height(70).fontColor(Color.White).focusOnTouch(false)
933      Button("id: " + this.idList[3] + " focusOnTouch(true)")
934        .width(400).height(70).fontColor(Color.White).focusOnTouch(true)
935    }.width('100%').margin({ top:20 })
936  }
937}
938```
939
940
941![zh-cn_image_0000001511580980](figures/zh-cn_image_0000001511580980.gif)
942
943
944解读:
945
946
947Button-A虽然设置了focusOnTouch(true),但是同时也设置了focusable(false),该组件无法获焦,因此点击后也无法获焦;
948
949
950Button-B不设置相关属性,点击后不会获焦;
951
952
953Button-C设置了focusOnTouch(false),同Button-B,点击后也不会获焦;
954
955
956Button-D设置了focusOnTouch(true),点击即可使其获焦;
957
958
959>**说明:**
960>
961>由于焦点态的阐述的特性,焦点态在屏幕接收点击事件后会立即清除。因此该示例代码在每次点击后,需要再次按下TAB键使焦点态再次显示,才可知道当前焦点所在的组件。
962
963
964### focusControl.requestFocus
965
966
967```ts
968focusControl.requestFocus(id: string)
969```
970
971主动申请焦点能力的全局方法,参数:string,参数表示被申请组件的id(通用属性id设置的字符串)。
972
973
974使用方法为:在任意执行语句中调用该API,指定目标组件的id为方法参数,当程序执行到该语句时,会立即给指定的目标组件申请焦点。
975
976
977代码示例:
978
979
980
981```ts
982// requestFocus.ets
983import promptAction from '@ohos.promptAction';
984
985@Entry
986@Component
987struct RequestFocusExample {
988  @State idList: string[] = ['A', 'B', 'C', 'D', 'E', 'F', 'N']
989  @State requestId: number = 0
990
991  build() {
992    Column({ space:20 }){
993      Row({space: 5}) {
994        Button("id: " + this.idList[0] + " focusable(false)")
995          .width(200).height(70).fontColor(Color.White)
996          .id(this.idList[0])
997          .focusable(false)
998        Button("id: " + this.idList[1])
999          .width(200).height(70).fontColor(Color.White)
1000          .id(this.idList[1])
1001      }
1002      Row({space: 5}) {
1003        Button("id: " + this.idList[2])
1004          .width(200).height(70).fontColor(Color.White)
1005          .id(this.idList[2])
1006        Button("id: " + this.idList[3])
1007          .width(200).height(70).fontColor(Color.White)
1008          .id(this.idList[3])
1009      }
1010      Row({space: 5}) {
1011        Button("id: " + this.idList[4])
1012          .width(200).height(70).fontColor(Color.White)
1013          .id(this.idList[4])
1014        Button("id: " + this.idList[5])
1015          .width(200).height(70).fontColor(Color.White)
1016          .id(this.idList[5])
1017      }
1018    }.width('100%').margin({ top:20 })
1019    .onKeyEvent((e) => {
1020      if (e.keyCode >= 2017 && e.keyCode <= 2022) {
1021        this.requestId = e.keyCode - 2017;
1022      } else if (e.keyCode === 2030) {
1023        this.requestId = 6;
1024      } else {
1025        return;
1026      }
1027      if (e.type !== KeyType.Down) {
1028        return;
1029      }
1030      let res = focusControl.requestFocus(this.idList[this.requestId]);
1031      if (res) {
1032        promptAction.showToast({message: 'Request success'});
1033      } else {
1034        promptAction.showToast({message: 'Request failed'});
1035      }
1036    })
1037  }
1038}
1039```
1040
1041
1042![zh-cn_image_0000001562820905](figures/zh-cn_image_0000001562820905.gif)
1043
1044
1045解读:页面中共6个Button组件,其中Button-A组件设置了focusable(false),表示其不可获焦,在外部容器的onKeyEvent中,监听按键事件,当按下A ~ F按键时,分别去申请Button A ~ F 的焦点,另外按下N键,是给当前页面上不存在的id的组件去申请焦点。
1046
1047
10481. 按下TAB键,由于第一个组件Button-A设置了无法获焦,那么默认第二个组件Button-B获焦,Button-B展示焦点态样式;
1049
10502. 键盘上按下A键,申请Button-A的焦点,气泡显示Request failed,表示无法获取到焦点,焦点位置未改变;
1051
10523. 键盘上按下B键,申请Button-B的焦点,气泡显示Request success,表示获焦到了焦点,焦点位置原本就在Button-B,位置未改变;
1053
10544. 键盘上按下C键,申请Button-C的焦点,气泡显示Request success,表示获焦到了焦点,焦点位置从Button-B变更为Button-C;
1055
10565. 键盘上按下D键,申请Button-D的焦点,气泡显示Request success,表示获焦到了焦点,焦点位置从Button-C变更为Button-D;
1057
10586. 键盘上按下E键,申请Button-E的焦点,气泡显示Request success,表示获焦到了焦点,焦点位置从Button-D变更为Button-E;
1059
10607. 键盘上按下F键,申请Button-F的焦点,气泡显示Request success,表示获焦到了焦点,焦点位置从Button-E变更为Button-F;
1061
10628. 键盘上按下N键,申请未知组件的焦点,气泡显示Request failed,表示无法获取到焦点,焦点位置不变;
1063