焦點事件

2024-02-07 12:49 更新

基本概念

  • 焦點

    指向當前應(yīng)用界面上唯一的一個可交互元素,當用戶使用鍵盤、電視遙控器、車機搖桿/旋鈕等非指向性輸入設(shè)備與應(yīng)用程序進行間接交互時,基于焦點的導航和交互是重要的輸入手段。

  • 默認焦點

    應(yīng)用打開或切換頁面后,若當前頁上存在可獲焦的組件,則樹形結(jié)構(gòu)的組件樹中第一個可獲焦的組件默認獲得焦點??梢允褂?a rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" >自定義默認焦點進行自定義指定。

  • 獲焦

    指組件獲得了焦點,同一時刻,應(yīng)用中最多只有1個末端組件是獲焦的,且此時它的所有祖宗組件(整個組件鏈)均是獲焦的。當期望某個組件獲焦,須確保該組件及其所有的祖宗節(jié)點均是可獲焦的(focusable屬性為true)。

  • 失焦

    指組件從獲焦狀態(tài)變成了非獲焦狀態(tài),失去了焦點。組件失焦時,它的所有祖宗組件(失焦組件鏈)與新的獲焦組件鏈不相同的節(jié)點都會失焦。

  • 走焦

    表示焦點在當前應(yīng)用中轉(zhuǎn)移的過程,走焦會帶來原焦點組件的失焦和新焦點組件的獲焦。應(yīng)用中焦點發(fā)生變化的方式按行為可分為兩類:

    • 主動走焦:指開發(fā)者/用戶主觀的行為導致焦點移動,包含:外接鍵盤上按下TAB/方向鍵、使用requestFocus主動給指定組件申請焦點、組件focusOnTouch屬性為true后點擊組件。
    • 被動走焦:指組件焦點因其他操作被動的轉(zhuǎn)移焦點,此特性為焦點系統(tǒng)默認行為,無法由開發(fā)者自由設(shè)定,例如當使用if-else語句將處于獲焦的組件刪除/將處于獲焦的組件(或其父組件)置成不可獲焦時、當頁面切換時。
  • 焦點態(tài)

    獲焦組件的樣式,不同組件的焦點態(tài)樣式大同小異,默認情況下焦點態(tài)不顯示,僅使用外接鍵盤按下TAB鍵/方向鍵時才會觸發(fā)焦點態(tài)樣式出現(xiàn)。首次觸發(fā)焦點態(tài)顯示的TAB鍵/方向鍵不會觸發(fā)走焦。當應(yīng)用接收到點擊事件時(包括手指觸屏的按下事件和鼠標左鍵的按下事件),自動隱藏焦點態(tài)樣式。焦點態(tài)樣式由后端組件定義,開發(fā)者無法修改。

走焦規(guī)則

走焦規(guī)則是指用戶使用“TAB鍵/SHIFT+TAB鍵/方向鍵”主動進行走焦,或焦點系統(tǒng)在執(zhí)行被動走焦時的順序規(guī)則。組件的走焦規(guī)則默認由走焦系統(tǒng)定義,由焦點所在的容器決定。

  • 線性走焦:常見的容器有Flex、Row、Column、List,這些都是典型的單方向容器,組件在這些容器內(nèi)的排列都是線性的,那么走焦規(guī)則也是線性的。走焦的方向和方向鍵的方向一致。
    圖1 線性走焦示意圖

    例如Row容器,使用方向鍵左右(←/→)即可將焦點在相鄰的2個可獲焦組件之間來回切換。

  • 十字走焦:使用方向鍵上(↑)下(↓)左(←)右(→)可以使焦點在相鄰的組件上切換。典型的是Grid容器,如下圖:
    圖2 Grid組件十字走焦示意圖
    說明
    • TAB/SHIFT+TAB鍵在以上兩種走焦規(guī)則上的功能和方向鍵一致。TAB鍵等同于“先執(zhí)行方向鍵右,若無法走焦,再執(zhí)行方向鍵下”,SHIFT+TAB鍵等同于“先執(zhí)行方向鍵左,若無法走焦,再執(zhí)行方向鍵上”。
    • 觸發(fā)走焦的按鍵是按下的事件(DOWN事件)。
    • 刪除組件、設(shè)置組件無法獲焦后,會使用線性走焦規(guī)則,自動先往被刪除/Unfocusable組件的前置兄弟組件上走焦,無法走焦的話,再往后置兄弟組件上走焦。
  • tabIndex走焦:給組件設(shè)置tabIndex通用屬性,自定義組件的TAB鍵/SHIFT+TAB鍵的走焦順序。
  • 區(qū)域走焦:給容器組件設(shè)置tabIndex通用屬性,再結(jié)合groupDefaultFocus通用屬性,自定義容器區(qū)域的TAB鍵/SHIFT+TAB鍵的走焦順序和默認獲焦組件。
  • 走焦至容器組件規(guī)則:當焦點走焦到容器(該容器沒有配置groupDefaultFocus)上時,若該容器組件為首次獲焦,則會先計算目標容器組件的子組件的區(qū)域位置,得到距離目標容器中心點最近的子組件,焦點會走到目標容器上的該子組件上。若該容器非首次獲焦,焦點會自動走焦到上一次目標容器中獲焦的子組件。
  • 焦點交互:當某組件獲焦時,該組件的固有點擊任務(wù)或開發(fā)者綁定的onClick回調(diào)任務(wù),會自動掛載到空格/回車按鍵上,當按下按鍵時,任務(wù)就和手指/鼠標點擊一樣被執(zhí)行。
說明

本文涉及到的焦點均為組件焦點,另外一個焦點的概念是:窗口焦點,指向當前獲焦的窗口。當窗口失焦時,該窗口應(yīng)用中的所有獲焦組件全部失焦。

監(jiān)聽組件的焦點變化

  1. onFocus(event: () => void)

獲焦事件回調(diào),綁定該API的組件獲焦時,回調(diào)響應(yīng)。

  1. onBlur(event:() => void)

失焦事件回調(diào),綁定該API的組件失焦時,回調(diào)響應(yīng)。

onFocus和onBlur兩個接口通常成對使用,來監(jiān)聽組件的焦點變化。

以下示例代碼展示獲焦/失焦回調(diào)的使用方法:

  1. // xxx.ets
  2. @Entry
  3. @Component
  4. struct FocusEventExample {
  5. @State oneButtonColor: Color = Color.Gray;
  6. @State twoButtonColor: Color = Color.Gray;
  7. @State threeButtonColor: Color = Color.Gray;
  8. build() {
  9. Column({ space: 20 }) {
  10. // 通過外接鍵盤的上下鍵可以讓焦點在三個按鈕間移動,按鈕獲焦時顏色變化,失焦時變回原背景色
  11. Button('First Button')
  12. .width(260)
  13. .height(70)
  14. .backgroundColor(this.oneButtonColor)
  15. .fontColor(Color.Black)
  16. // 監(jiān)聽第一個組件的獲焦事件,獲焦后改變顏色
  17. .onFocus(() => {
  18. this.oneButtonColor = Color.Green;
  19. })
  20. // 監(jiān)聽第一個組件的失焦事件,失焦后改變顏色
  21. .onBlur(() => {
  22. this.oneButtonColor = Color.Gray;
  23. })
  24. Button('Second Button')
  25. .width(260)
  26. .height(70)
  27. .backgroundColor(this.twoButtonColor)
  28. .fontColor(Color.Black)
  29. // 監(jiān)聽第二個組件的獲焦事件,獲焦后改變顏色
  30. .onFocus(() => {
  31. this.twoButtonColor = Color.Green;
  32. })
  33. // 監(jiān)聽第二個組件的失焦事件,失焦后改變顏色
  34. .onBlur(() => {
  35. this.twoButtonColor = Color.Grey;
  36. })
  37. Button('Third Button')
  38. .width(260)
  39. .height(70)
  40. .backgroundColor(this.threeButtonColor)
  41. .fontColor(Color.Black)
  42. // 監(jiān)聽第三個組件的獲焦事件,獲焦后改變顏色
  43. .onFocus(() => {
  44. this.threeButtonColor = Color.Green;
  45. })
  46. // 監(jiān)聽第三個組件的失焦事件,失焦后改變顏色
  47. .onBlur(() => {
  48. this.threeButtonColor = Color.Gray ;
  49. })
  50. }.width('100%').margin({ top: 20 })
  51. }
  52. }

上述示例包含以下4步:

  1. 應(yīng)用打開時,“First Button”默認獲取焦點,onFocus回調(diào)響應(yīng),背景色變成綠色。
  2. 按下TAB鍵(或方向鍵下↓),“First Button”顯示焦點態(tài)樣式:組件外圍有一個藍色的閉合框。不觸發(fā)走焦,焦點仍然在“First Button”上。
  3. 按下TAB鍵(或方向鍵下↓),觸發(fā)走焦,“Second Button”獲焦,onFocus回調(diào)響應(yīng),背景色變成綠色;“First Button”失焦、onBlur回調(diào)響應(yīng),背景色變回灰色。
  4. 按下TAB鍵(或方向鍵下↓),觸發(fā)走焦,“Third Button”獲焦,onFocus回調(diào)響應(yīng),背景色變成綠色;“Second Button”失焦、onBlur回調(diào)響應(yīng),背景色變回灰色。

設(shè)置組件是否獲焦

通過focusable接口設(shè)置組件是否可獲焦:

  1. focusable(value: boolean)

按照組件的獲焦能力可大致分為三類:

  • 默認可獲焦的組件,通常是有交互行為的組件,例如Button、Checkbox,TextInput組件,此類組件無需設(shè)置任何屬性,默認即可獲焦。
  • 有獲焦能力,但默認不可獲焦的組件,典型的是Text、Image組件,此類組件缺省情況下無法獲焦,若需要使其獲焦,可使用通用屬性focusable(true)使能。
  • 無獲焦能力的組件,通常是無任何交互行為的展示類組件,例如Blank、Circle組件,此類組件即使使用focusable屬性也無法使其可獲焦。
說明
  • focusable為false表示組件不可獲焦,同樣可以使組件變成不可獲焦的還有通用屬性enabled。
  • 當某組件處于獲焦狀態(tài)時,將其的focusable屬性或enabled屬性設(shè)置為false,會自動使該組件失焦,然后焦點按照走焦規(guī)則將焦點轉(zhuǎn)移給其他組件。
表1 基礎(chǔ)組件獲焦能力

基礎(chǔ)組件

是否有獲焦能力

focusable默認值

走焦規(guī)則

AlphabetIndexer

true

線性走焦

Blank

false

/

Button

true

/

Checkbox

true

/

CheckboxGroup

true

/

DataPanel

false

/

DatePicker

true

線性走焦

Divider

false

/

Gauge

false

/

Image

false

/

ImageAnimator

false

/

LoadingProgress

false

/

Marquee

false

/

Menu

true

線性走焦

MenuItem

true

/

MenuItemGroup

true

線性走焦

Navigation

false

組件自定義

NavRouter

false

跟隨子容器

NavDestination

false

線性走焦

PatternLock

false

/

Progress

false

/

QRCode

false

/

Radio

true

/

Rating

true

/

RichText

false

/

ScrollBar

false

/

Search

true

/

Select

true

線性走焦

Slider

true

/

Span

false

/

Stepper

true

/

StepperItem

true

/

Text

false

/

TextArea

true

/

TextClock

false

/

TextInput

true

/

TextPicker

true

線性走焦

TextTimer

false

/

TimePicker

true

線性走焦

Toggle

true

/

Web

true

Web組件自定義

XComponent

false

/

表2 容器組件獲焦能力

容器組件

是否可獲焦

focusable默認值

走焦規(guī)則

Badge

false

/

Column

true

線性走焦

ColumnSplit

true

/

Counter

true

線性走焦

Flex

true

線性走焦

GridCol

true

容器組件自定義

GridRow

true

容器組件自定義

Grid

true

容器組件自定義

GridItem

true

跟隨子組件

List

true

線性走焦

ListItem

true

跟隨子組件

ListItemGroup

true

跟隨List組件

Navigator

true

容器組件自定義

Panel

true

跟隨子組件

Refresh

false

/

RelativeContainer

true

容器組件自定義

Row

true

線性走焦

RowSplit

true

/

Scroll

true

線性走焦

SideBarContainer

true

線性走焦

Stack

true

線性走焦

Swiper

true

容器組件自定義

Tabs

true

容器組件自定義

TabContent

true

跟隨子組件

表3 媒體組件獲焦能力

媒體組件

是否可獲焦

focusable默認值

走焦規(guī)則

Video

true

/

表4 畫布組件獲焦能力

畫布組件

是否可獲焦

focusable默認值

走焦規(guī)則

Canvas

false

/

以下示例為大家展示focusable接口的使用方法:

  1. // xxx.ets
  2. @Entry
  3. @Component
  4. struct FocusableExample {
  5. @State textFocusable: boolean = true;
  6. @State color1: Color = Color.Yellow;
  7. @State color2: Color = Color.Yellow;
  8. build() {
  9. Column({ space: 5 }) {
  10. Text('Default Text') // 第一個Text組件未設(shè)置focusable屬性,默認不可獲焦
  11. .borderColor(this.color1)
  12. .borderWidth(2)
  13. .width(300)
  14. .height(70)
  15. .onFocus(() => {
  16. this.color1 = Color.Blue;
  17. })
  18. .onBlur(() => {
  19. this.color1 = Color.Yellow;
  20. })
  21. Divider()
  22. Text('focusable: ' + this.textFocusable) // 第二個Text設(shè)置了focusable屬性,初始值為true
  23. .borderColor(this.color2)
  24. .borderWidth(2)
  25. .width(300)
  26. .height(70)
  27. .focusable(this.textFocusable)
  28. .onFocus(() => {
  29. this.color2 = Color.Blue;
  30. })
  31. .onBlur(() => {
  32. this.color2 = Color.Yellow;
  33. })
  34. Divider()
  35. Row() {
  36. Button('Button1')
  37. .width(140).height(70)
  38. Button('Button2')
  39. .width(160).height(70)
  40. }
  41. Divider()
  42. Button('Button3')
  43. .width(300).height(70)
  44. Divider()
  45. }.width('100%').justifyContent(FlexAlign.Center)
  46. .onKeyEvent((e) => { // 綁定onKeyEvent,在該Column組件獲焦時,按下'F'鍵,可將第二個Text的focusable置反
  47. if (e.keyCode === 2022 && e.type === KeyType.Down) {
  48. this.textFocusable = !this.textFocusable;
  49. }
  50. })
  51. }
  52. }

運行效果:

上述示例包含默認獲焦和主動走焦兩部分:

默認獲焦:

  • 根據(jù)默認焦點的說明,該應(yīng)用打開后,默認第一個可獲焦元素獲焦:
  • 第一個Text組件沒有設(shè)置focusable(true)屬性,該Text組件無法獲焦。
  • 第二個Text組件的focusable屬性顯式設(shè)置為true,說明該組件可獲焦,那么默認焦點將置到它身上。

主動走焦:

按鍵盤F鍵,觸發(fā)onKeyEvent,focusable置為false,Text組件變成不可獲焦,焦點自動轉(zhuǎn)移,按照被動走焦中的說明項,焦點會自動從Text組件先向上尋找下一個可獲焦組件,由于上一個組件是一個不可獲焦的Text,所以向下尋找下一個可獲焦的組件,找到并使焦點轉(zhuǎn)移到Row容器上,根據(jù)走焦至容器規(guī)則,計算Button1和Button2的位置,Button2比Button1更大,因此焦點會自動轉(zhuǎn)移到Button2上。

自定義默認焦點

  1. defaultFocus(value: boolean)

焦點系統(tǒng)在頁面初次構(gòu)建完成時,會搜索當前頁下的所有組件,找到第一個綁定了defaultFocus(true)的組件,然后將該組件置為默認焦點,若無任何組件綁定defaultFocus(true),則將第一個找到的可獲焦的組件置為默認焦點。

以如下應(yīng)用為例,應(yīng)用布局如下:

以下是實現(xiàn)該應(yīng)用的示例代碼,且示例代碼中沒有設(shè)置defaultFocus:

  1. // xxx.ets
  2. import promptAction from '@ohos.promptAction';
  3. class MyDataSource implements IDataSource {
  4. private list: number[] = [];
  5. private listener: DataChangeListener;
  6. constructor(list: number[]) {
  7. this.list = list;
  8. }
  9. totalCount(): number {
  10. return this.list.length;
  11. }
  12. getData(index: number): any {
  13. return this.list[index];
  14. }
  15. registerDataChangeListener(listener: DataChangeListener): void {
  16. this.listener = listener;
  17. }
  18. unregisterDataChangeListener() {
  19. }
  20. }
  21. @Entry
  22. @Component
  23. struct SwiperExample {
  24. private swiperController: SwiperController = new SwiperController()
  25. private data: MyDataSource = new MyDataSource([])
  26. aboutToAppear(): void {
  27. let list = []
  28. for (let i = 1; i <= 4; i++) {
  29. list.push(i.toString());
  30. }
  31. this.data = new MyDataSource(list);
  32. }
  33. build() {
  34. Column({ space: 5 }) {
  35. Swiper(this.swiperController) {
  36. LazyForEach(this.data, (item: string) => {
  37. Row({ space: 20 }) {
  38. Column() {
  39. Button('1').width(200).height(200)
  40. .fontSize(40)
  41. .backgroundColor('#dadbd9')
  42. }
  43. Column({ space: 20 }) {
  44. Row({ space: 20 }) {
  45. Button('2')
  46. .width(100)
  47. .height(100)
  48. .fontSize(40)
  49. .type(ButtonType.Normal)
  50. .borderRadius(20)
  51. .backgroundColor('#dadbd9')
  52. Button('3')
  53. .width(100)
  54. .height(100)
  55. .fontSize(40)
  56. .type(ButtonType.Normal)
  57. .borderRadius(20)
  58. .backgroundColor('#dadbd9')
  59. }
  60. Row({ space: 20 }) {
  61. Button('4')
  62. .width(100)
  63. .height(100)
  64. .fontSize(40)
  65. .type(ButtonType.Normal)
  66. .borderRadius(20)
  67. .backgroundColor('#dadbd9')
  68. Button('5')
  69. .width(100)
  70. .height(100)
  71. .fontSize(40)
  72. .type(ButtonType.Normal)
  73. .borderRadius(20)
  74. .backgroundColor('#dadbd9')
  75. }
  76. Row({ space: 20 }) {
  77. Button('6')
  78. .width(100)
  79. .height(100)
  80. .fontSize(40)
  81. .type(ButtonType.Normal)
  82. .borderRadius(20)
  83. .backgroundColor('#dadbd9')
  84. Button('7')
  85. .width(100)
  86. .height(100)
  87. .fontSize(40)
  88. .type(ButtonType.Normal)
  89. .borderRadius(20)
  90. .backgroundColor('#dadbd9')
  91. }
  92. }
  93. }
  94. .width(480)
  95. .height(380)
  96. .justifyContent(FlexAlign.Center)
  97. .borderWidth(2)
  98. .borderColor(Color.Gray)
  99. .backgroundColor(Color.White)
  100. }, item => item)
  101. }
  102. .cachedCount(2)
  103. .index(0)
  104. .interval(4000)
  105. .indicator(true)
  106. .loop(true)
  107. .duration(1000)
  108. .itemSpace(0)
  109. .curve(Curve.Linear)
  110. .onChange((index: number) => {
  111. console.info(index.toString());
  112. })
  113. .margin({ left: 20, top: 20, right: 20 })
  114. Row({ space: 40 }) {
  115. Button('←')
  116. .fontSize(40)
  117. .fontWeight(FontWeight.Bold)
  118. .fontColor(Color.Black)
  119. .backgroundColor(Color.Transparent)
  120. .onClick(() => {
  121. this.swiperController.showPrevious();
  122. })
  123. Button('→')
  124. .fontSize(40)
  125. .fontWeight(FontWeight.Bold)
  126. .fontColor(Color.Black)
  127. .backgroundColor(Color.Transparent)
  128. .onClick(() => {
  129. this.swiperController.showNext();
  130. })
  131. }
  132. .width(480)
  133. .height(50)
  134. .justifyContent(FlexAlign.Center)
  135. .borderWidth(2)
  136. .borderColor(Color.Gray)
  137. .backgroundColor('#f7f6dc')
  138. Row({ space: 40 }) {
  139. Button('Cancel')
  140. .fontSize(30)
  141. .fontColor('#787878')
  142. .type(ButtonType.Normal)
  143. .width(140)
  144. .height(50)
  145. .backgroundColor('#dadbd9')
  146. Button('OK')
  147. .fontSize(30)
  148. .fontColor('#787878')
  149. .type(ButtonType.Normal)
  150. .width(140)
  151. .height(50)
  152. .backgroundColor('#dadbd9')
  153. .onClick(() => {
  154. promptAction.showToast({ message: 'Button OK on clicked' });
  155. })
  156. }
  157. .width(480)
  158. .height(80)
  159. .justifyContent(FlexAlign.Center)
  160. .borderWidth(2)
  161. .borderColor(Color.Gray)
  162. .backgroundColor('#dff2e4')
  163. .margin({ left: 20, bottom: 20, right: 20 })
  164. }.backgroundColor('#f2f2f2')
  165. .margin({ left: 50, top: 50, right: 20 })
  166. }
  167. }

當前應(yīng)用上無任何defaultFocus設(shè)置,所以第一個可獲焦的組件默認獲取焦點,按下TAB鍵/方向鍵讓獲焦的組件顯示焦點態(tài)樣式:

假設(shè)開發(fā)者想讓應(yīng)用打開的時候,無需執(zhí)行多余的切換焦點操作,直接點擊按鍵的空格/回車鍵,就可以執(zhí)行Button-OK的onClick回調(diào)操作,那么就可以給這個Button綁定defaultFocus(true),讓它成為該頁面上的默認焦點:

  1. Button('OK')
  2. .defaultFocus(true) // 設(shè)置Button-OK為defaultFocus
  3. .fontSize(30)
  4. .fontColor('#787878')
  5. .type(ButtonType.Normal)
  6. .width(140).height(50).backgroundColor('#dadbd9')
  7. .onClick(() => {
  8. promptAction.showToast({ message: 'Button OK on clicked' });
  9. })

打開應(yīng)用后按TAB鍵,Button-OK顯示了焦點態(tài),說明默認焦點變更到了Button-OK上。然后按下空格,響應(yīng)了Button-OK的onClick事件。

自定義TAB鍵走焦順序

  1. tabIndex(index: number)

tabIndex用于設(shè)置自定義TAB鍵走焦順序,默認值為0。使用“TAB/Shift+TAB鍵”走焦時(方向鍵不影響),系統(tǒng)會自動獲取到所有配置了tabIndex大于0的組件,然后按照遞增/遞減排序進行走焦。

defaultFocus提供的示例為例,默認情況下的走焦順序如下:

默認的走焦順序從第一個獲焦組件一路走到最后一個獲焦組件,會經(jīng)歷Button1->Button4->Button5->Button7->左箭頭->右箭頭->ButtonOK。這種走焦隊列比較完整,遍歷了大部分的組件。但缺點是從第一個走到最后一個所經(jīng)歷的路徑較長。

如果想實現(xiàn)快速的從第一個走到最后一個,又不想犧牲太多的遍歷完整性,就可以使用tabIndex通用屬性。

比如:開發(fā)者把白色的區(qū)域當為一個整體,黃色的區(qū)域當為一個整體,綠色的區(qū)域當為一個整體,實現(xiàn)Button1->左箭頭->ButtonOK這種隊列的走焦順序,只需要在Button1、左箭頭、ButtonOK這三個組件上依次增加tabIndex(1)、tabIndex(2)、tabIndex(3)。tabIndex的參數(shù)表示TAB走焦的順序(從大于0的數(shù)字開始,從小到大排列)。

  1. Button('1').width(200).height(200)
  2. .fontSize(40)
  3. .backgroundColor('#dadbd9')
  4. .tabIndex(1) // Button-1設(shè)置為第一個tabIndex節(jié)點
  1. Button('←')
  2. .fontSize(40)
  3. .fontWeight(FontWeight.Bold)
  4. .fontColor(Color.Black)
  5. .backgroundColor(Color.Transparent)
  6. .onClick(() => {
  7. this.swiperController.showPrevious();
  8. })
  9. .tabIndex(2) // Button-左箭頭設(shè)置為第二個tabIndex節(jié)點
  1. Button('OK')
  2. .fontSize(30)
  3. .fontColor('#787878')
  4. .type(ButtonType.Normal)
  5. .width(140).height(50).backgroundColor('#dadbd9')
  6. .onClick(() => {
  7. promptAction.showToast({ message: 'Button OK on clicked' });
  8. })
  9. .tabIndex(3) // Button-OK設(shè)置為第三個tabIndex節(jié)點

說明
  • 當焦點處于tabIndex(大于0)節(jié)點上時,TAB/ShiftTAB會優(yōu)先在tabIndex(大于0)的隊列中尋找后置/前置的節(jié)點,存在則走焦至相應(yīng)的tabIndex節(jié)點。若不存在,則使用默認的走焦邏輯繼續(xù)往后/往前走焦。
  • 當焦點處于tabIndex(等于0)節(jié)點上時,TAB/ShiftTAB使用默認的走焦邏輯走焦,走焦的過程中會跳過tabIndex(大于0)和tabIndex(小于0)的節(jié)點。
  • 當焦點處于tabIndex(小于0)節(jié)點上時,TAB/ShiftTAB無法走焦。

groupDefaultFocus

  1. groupDefaultFocus(value: boolean)

自定義TAB鍵走焦順序中所展示的使用tabIndex完成快速走焦的能力有如下問題:

每個區(qū)域(白色/黃色/綠色三個區(qū)域)都設(shè)置了某個組件為tabIndex節(jié)點(白色-Button1、黃色-左箭頭、綠色-ButtonOK),但這樣設(shè)置之后,只能在這3個組件上按TAB/ShiftTab鍵走焦時會有快速走焦的效果。

解決方案是給每個區(qū)域的容器設(shè)置tabIndex,但是這樣設(shè)置的問題是:第一次走焦到容器上時,獲焦的子組件是默認的第一個可獲焦組件,并不是自己想要的組件(Button1、左箭頭、ButtonOK)。

這樣便引入了groupDefaultFocus通用屬性,參數(shù):boolean,默認值:false。

用法需和tabIndex組合使用,使用tabIndex給區(qū)域(容器)綁定走焦順序,然后給Button1、左箭頭、ButtonOK綁定groupDefaultFocus(true),這樣在首次走焦到目標區(qū)域(容器)上時,它的綁定了groupDefaultFocus(true)的子組件同時獲得焦點。

  1. // xxx.ets
  2. import promptAction from '@ohos.promptAction';
  3. class MyDataSource implements IDataSource {
  4. private list: number[] = [];
  5. private listener: DataChangeListener;
  6. constructor(list: number[]) {
  7. this.list = list;
  8. }
  9. totalCount(): number {
  10. return this.list.length;
  11. }
  12. getData(index: number): any {
  13. return this.list[index];
  14. }
  15. registerDataChangeListener(listener: DataChangeListener): void {
  16. this.listener = listener;
  17. }
  18. unregisterDataChangeListener() {
  19. }
  20. }
  21. @Entry
  22. @Component
  23. struct SwiperExample {
  24. private swiperController: SwiperController = new SwiperController()
  25. private data: MyDataSource = new MyDataSource([])
  26. aboutToAppear(): void {
  27. let list = []
  28. for (let i = 1; i <= 4; i++) {
  29. list.push(i.toString());
  30. }
  31. this.data = new MyDataSource(list);
  32. }
  33. build() {
  34. Column({ space: 5 }) {
  35. Swiper(this.swiperController) {
  36. LazyForEach(this.data, (item: string) => {
  37. Row({ space: 20 }) { // 設(shè)置該Row組件為tabIndex的第一個節(jié)點
  38. Column() {
  39. Button('1').width(200).height(200)
  40. .fontSize(40)
  41. .backgroundColor('#dadbd9')
  42. .groupDefaultFocus(true) // 設(shè)置Button-1為第一個tabIndex的默認焦點
  43. }
  44. Column({ space: 20 }) {
  45. Row({ space: 20 }) {
  46. Button('2')
  47. .width(100)
  48. .height(100)
  49. .fontSize(40)
  50. .type(ButtonType.Normal)
  51. .borderRadius(20)
  52. .backgroundColor('#dadbd9')
  53. Button('3')
  54. .width(100)
  55. .height(100)
  56. .fontSize(40)
  57. .type(ButtonType.Normal)
  58. .borderRadius(20)
  59. .backgroundColor('#dadbd9')
  60. }
  61. Row({ space: 20 }) {
  62. Button('4')
  63. .width(100)
  64. .height(100)
  65. .fontSize(40)
  66. .type(ButtonType.Normal)
  67. .borderRadius(20)
  68. .backgroundColor('#dadbd9')
  69. Button('5')
  70. .width(100)
  71. .height(100)
  72. .fontSize(40)
  73. .type(ButtonType.Normal)
  74. .borderRadius(20)
  75. .backgroundColor('#dadbd9')
  76. }
  77. Row({ space: 20 }) {
  78. Button('6')
  79. .width(100)
  80. .height(100)
  81. .fontSize(40)
  82. .type(ButtonType.Normal)
  83. .borderRadius(20)
  84. .backgroundColor('#dadbd9')
  85. Button('7')
  86. .width(100)
  87. .height(100)
  88. .fontSize(40)
  89. .type(ButtonType.Normal)
  90. .borderRadius(20)
  91. .backgroundColor('#dadbd9')
  92. }
  93. }
  94. }
  95. .width(480)
  96. .height(380)
  97. .justifyContent(FlexAlign.Center)
  98. .borderWidth(2)
  99. .borderColor(Color.Gray)
  100. .backgroundColor(Color.White)
  101. .tabIndex(1)
  102. }, item => item)
  103. }
  104. .cachedCount(2)
  105. .index(0)
  106. .interval(4000)
  107. .indicator(true)
  108. .loop(true)
  109. .duration(1000)
  110. .itemSpace(0)
  111. .curve(Curve.Linear)
  112. .onChange((index: number) => {
  113. console.info(index.toString());
  114. })
  115. .margin({ left: 20, top: 20, right: 20 })
  116. Row({ space: 40 }) { // 設(shè)置該Row組件為第二個tabIndex節(jié)點
  117. Button('←')
  118. .fontSize(40)
  119. .fontWeight(FontWeight.Bold)
  120. .fontColor(Color.Black)
  121. .backgroundColor(Color.Transparent)
  122. .onClick(() => {
  123. this.swiperController.showPrevious();
  124. })
  125. .groupDefaultFocus(true) // 設(shè)置Button-左箭頭為第二個tabIndex節(jié)點的默認焦點
  126. Button('→')
  127. .fontSize(40)
  128. .fontWeight(FontWeight.Bold)
  129. .fontColor(Color.Black)
  130. .backgroundColor(Color.Transparent)
  131. .onClick(() => {
  132. this.swiperController.showNext();
  133. })
  134. }
  135. .width(480)
  136. .height(50)
  137. .justifyContent(FlexAlign.Center)
  138. .borderWidth(2)
  139. .borderColor(Color.Gray)
  140. .backgroundColor('#f7f6dc')
  141. .tabIndex(2)
  142. Row({ space: 40 }) { // 設(shè)置該Row組件為第三個tabIndex節(jié)點
  143. Button('Cancel')
  144. .fontSize(30)
  145. .fontColor('#787878')
  146. .type(ButtonType.Normal)
  147. .width(140)
  148. .height(50)
  149. .backgroundColor('#dadbd9')
  150. Button('OK')
  151. .fontSize(30)
  152. .fontColor('#787878')
  153. .type(ButtonType.Normal)
  154. .width(140)
  155. .height(50)
  156. .backgroundColor('#dadbd9')
  157. .defaultFocus(true)
  158. .onClick(() => {
  159. promptAction.showToast({ message: 'Button OK on clicked' });
  160. })
  161. .groupDefaultFocus(true) // 設(shè)置Button-OK為第三個tabIndex節(jié)點的默認焦點
  162. }
  163. .width(480)
  164. .height(80)
  165. .justifyContent(FlexAlign.Center)
  166. .borderWidth(2)
  167. .borderColor(Color.Gray)
  168. .backgroundColor('#dff2e4')
  169. .margin({ left: 20, bottom: 20, right: 20 })
  170. .tabIndex(3)
  171. }.backgroundColor('#f2f2f2')
  172. .margin({ left: 50, top: 50, right: 20 })
  173. }
  174. }

focusOnTouch

  1. focusOnTouch(value: boolean)

點擊獲焦能力,參數(shù):boolean,默認值:false(輸入類組件:TextInput、TextArea、Search、Web默認值是true)。

點擊是指使用觸屏或鼠標左鍵進行單擊,默認為false的組件,例如Button,不綁定該API時,點擊Button不會使其獲焦,當給Button綁定focusOnTouch(true)時,點擊Button會使Button立即獲得焦點。

給容器綁定focusOnTouch(true)時,點擊容器區(qū)域,會立即使容器的第一個可獲焦組件獲得焦點。

示例代碼:

  1. // requestFocus.ets
  2. import promptAction from '@ohos.promptAction';
  3. @Entry
  4. @Component
  5. struct RequestFocusExample {
  6. @State idList: string[] = ['A', 'B', 'C', 'D', 'E', 'F', 'N']
  7. build() {
  8. Column({ space:20 }){
  9. Button("id: " + this.idList[0] + " focusOnTouch(true) + focusable(false)")
  10. .width(400).height(70).fontColor(Color.White).focusOnTouch(true)
  11. .focusable(false)
  12. Button("id: " + this.idList[1] + " default")
  13. .width(400).height(70).fontColor(Color.White)
  14. Button("id: " + this.idList[2] + " focusOnTouch(false)")
  15. .width(400).height(70).fontColor(Color.White).focusOnTouch(false)
  16. Button("id: " + this.idList[3] + " focusOnTouch(true)")
  17. .width(400).height(70).fontColor(Color.White).focusOnTouch(true)
  18. }.width('100%').margin({ top:20 })
  19. }
  20. }

效果:

解讀:

Button-A雖然設(shè)置了focusOnTouch(true),但是同時也設(shè)置了focusable(false),該組件無法獲焦,因此點擊后也無法獲焦;

Button-B不設(shè)置相關(guān)屬性,點擊后不會獲焦;

Button-C設(shè)置了focusOnTouch(false),同Button-B,點擊后也不會獲焦;

Button-D設(shè)置了focusOnTouch(true),點擊即可使其獲焦;

說明

由于焦點態(tài)的闡述的特性,焦點態(tài)在屏幕接收點擊事件后會立即清除。因此該示例代碼在每次點擊后,需要再次按下TAB鍵使焦點態(tài)再次顯示,才可知道當前焦點所在的組件。

focusControl.requestFocus

  1. focusControl.requestFocus(id: string)

主動申請焦點能力的全局方法,參數(shù):string,參數(shù)表示被申請組件的id(通用屬性id設(shè)置的字符串)。

使用方法為:在任意執(zhí)行語句中調(diào)用該API,指定目標組件的id為方法參數(shù),當程序執(zhí)行到該語句時,會立即給指定的目標組件申請焦點。

代碼示例:

  1. // requestFocus.ets
  2. import promptAction from '@ohos.promptAction';
  3. @Entry
  4. @Component
  5. struct RequestFocusExample {
  6. @State idList: string[] = ['A', 'B', 'C', 'D', 'E', 'F', 'N']
  7. @State requestId: number = 0
  8. build() {
  9. Column({ space:20 }){
  10. Row({space: 5}) {
  11. Button("id: " + this.idList[0] + " focusable(false)")
  12. .width(200).height(70).fontColor(Color.White)
  13. .id(this.idList[0])
  14. .focusable(false)
  15. Button("id: " + this.idList[1])
  16. .width(200).height(70).fontColor(Color.White)
  17. .id(this.idList[1])
  18. }
  19. Row({space: 5}) {
  20. Button("id: " + this.idList[2])
  21. .width(200).height(70).fontColor(Color.White)
  22. .id(this.idList[2])
  23. Button("id: " + this.idList[3])
  24. .width(200).height(70).fontColor(Color.White)
  25. .id(this.idList[3])
  26. }
  27. Row({space: 5}) {
  28. Button("id: " + this.idList[4])
  29. .width(200).height(70).fontColor(Color.White)
  30. .id(this.idList[4])
  31. Button("id: " + this.idList[5])
  32. .width(200).height(70).fontColor(Color.White)
  33. .id(this.idList[5])
  34. }
  35. }.width('100%').margin({ top:20 })
  36. .onKeyEvent((e) => {
  37. if (e.keyCode >= 2017 && e.keyCode <= 2022) {
  38. this.requestId = e.keyCode - 2017;
  39. } else if (e.keyCode === 2030) {
  40. this.requestId = 6;
  41. } else {
  42. return;
  43. }
  44. if (e.type !== KeyType.Down) {
  45. return;
  46. }
  47. let res = focusControl.requestFocus(this.idList[this.requestId]);
  48. if (res) {
  49. promptAction.showToast({message: 'Request success'});
  50. } else {
  51. promptAction.showToast({message: 'Request failed'});
  52. }
  53. })
  54. }
  55. }

效果:

解讀:頁面中共6個Button組件,其中Button-A組件設(shè)置了focusable(false),表示其不可獲焦,在外部容器的onKeyEvent中,監(jiān)聽按鍵事件,當按下A ~ F按鍵時,分別去申請Button A ~ F 的焦點,另外按下N鍵,是給當前頁面上不存在的id的組件去申請焦點。

  1. 按下TAB鍵,由于第一個組件Button-A設(shè)置了無法獲焦,那么默認第二個組件Button-B獲焦,Button-B展示焦點態(tài)樣式;
  2. 鍵盤上按下A鍵,申請Button-A的焦點,氣泡顯示Request failed,表示無法獲取到焦點,焦點位置未改變;
  3. 鍵盤上按下B鍵,申請Button-B的焦點,氣泡顯示Request success,表示獲焦到了焦點,焦點位置原本就在Button-B,位置未改變;
  4. 鍵盤上按下C鍵,申請Button-C的焦點,氣泡顯示Request success,表示獲焦到了焦點,焦點位置從Button-B變更為Button-C;
  5. 鍵盤上按下D鍵,申請Button-D的焦點,氣泡顯示Request success,表示獲焦到了焦點,焦點位置從Button-C變更為Button-D;
  6. 鍵盤上按下E鍵,申請Button-E的焦點,氣泡顯示Request success,表示獲焦到了焦點,焦點位置從Button-D變更為Button-E;
  7. 鍵盤上按下F鍵,申請Button-F的焦點,氣泡顯示Request success,表示獲焦到了焦點,焦點位置從Button-E變更為Button-F;
  8. 鍵盤上按下N鍵,申請未知組件的焦點,氣泡顯示Request failed,表示無法獲取到焦點,焦點位置不變;
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號