经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » 鸿蒙 » 查看文章
鸿蒙HarmonyOS实战-ArkUI事件(焦点事件)
来源:cnblogs  作者:蜀道山QAQ  时间:2024/5/8 9:12:05  对本文有异议

??前言

焦点事件是指程序中的重要事件或关键点。焦点事件通常是程序的核心逻辑和功能,需要引起特殊的关注和处理。

在图形用户界面(GUI)编程中,焦点事件通常与用户交互和界面输入相关。例如,当用户点击按钮、输入文本或选择菜单项时,这些操作会触发相应的焦点事件。程序需要捕获这些焦点事件并进行处理,以执行相应的操作或响应用户的请求。

另外,在事件驱动的编程模型中,焦点事件也与程序的流程控制和状态转换有关。例如,当某个关键条件满足时,程序会触发相应的焦点事件,然后根据这些事件来执行特定的操作或改变程序的状态。

??一、焦点事件

??1.基本概念

焦点事件基本概念是指在用户界面中,焦点在不同控件之间切换时,触发的相关事件。下面是一些焦点事件的基本概念:

  1. 焦点(Focus):焦点是指用户当前正在与之交互的控件或元素。例如,在一个表单中,焦点可能位于输入框、复选框或按钮等控件上。焦点通常用来表示哪个控件可以接收用户的输入。

  2. 默认焦点(Default Focus):默认焦点是指用户在进入一个界面或打开一个应用程序时,自动设置在界面中某个控件上的焦点。默认焦点通常是用来提高用户交互的效率,使用户可以直接开始输入或选择操作。

  3. 获焦(Focus Gained):获焦是指当一个控件或元素成为焦点时触发的事件。获焦事件通常可以用来执行一些初始化操作,例如设置焦点控件的样式或加载数据。

  4. 失焦(Focus Lost):失焦是指当一个控件或元素不再是焦点时触发的事件。失焦事件通常可以用来执行一些清理操作,例如保存用户输入或验证输入数据。

  5. 走焦(Traversal):走焦是指焦点在控件之间切换的过程。焦点可以通过按下Tab键或者使用方向键来在不同的控件之间移动。

  6. 焦点态(Focus State):焦点态是指控件或元素在成为焦点或失去焦点时,其外观或状态发生的变化。焦点态可以用来提高用户交互的可见性,例如高亮显示焦点控件或显示输入光标。

焦点事件基本概念涉及到焦点的获取、失去和切换,以及与焦点相关的事件和状态。

??2.走焦规则

image

??3.监听组件的焦点变化

接口定义:

  1. onFocus(event: () => void)//获焦事件回调
  2. onBlur(event:() => void)//失焦事件回调

案例:

  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. // 监听第一个组件的获焦事件,获焦后改变颜色
  17. .onFocus(() => {
  18. this.oneButtonColor = Color.Green;
  19. })
  20. // 监听第一个组件的失焦事件,失焦后改变颜色
  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. // 监听第二个组件的获焦事件,获焦后改变颜色
  30. .onFocus(() => {
  31. this.twoButtonColor = Color.Green;
  32. })
  33. // 监听第二个组件的失焦事件,失焦后改变颜色
  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. // 监听第三个组件的获焦事件,获焦后改变颜色
  43. .onFocus(() => {
  44. this.threeButtonColor = Color.Green;
  45. })
  46. // 监听第三个组件的失焦事件,失焦后改变颜色
  47. .onBlur(() => {
  48. this.threeButtonColor = Color.Gray ;
  49. })
  50. }.width('100%').margin({ top: 20 })
  51. }
  52. }

image

??4.设置组件是否获焦

image

按照组件的获焦能力分为三类的表格展示,可以根据需要选择适合的组件类型来实现焦点控制功能。

接口:

  1. focusable(value: boolean)

案例:

  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组件未设置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设置了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. }

image

??5.自定义默认焦点

接口:

  1. defaultFocus(value: boolean)

案例:

  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. }

image

??6.自定义TAB键走焦顺序

  1. Button('1').width(200).height(200)
  2. .fontSize(40)
  3. .backgroundColor('#dadbd9')
  4. .tabIndex(1) // Button-1设置为第一个tabIndex节点
  5. Button('←')
  6. .fontSize(40)
  7. .fontWeight(FontWeight.Bold)
  8. .fontColor(Color.Black)
  9. .backgroundColor(Color.Transparent)
  10. .onClick(() => {
  11. this.swiperController.showPrevious();
  12. })
  13. .tabIndex(2) // Button-左箭头设置为第二个tabIndex节点
  14. Button('OK')
  15. .fontSize(30)
  16. .fontColor('#787878')
  17. .type(ButtonType.Normal)
  18. .width(140).height(50).backgroundColor('#dadbd9')
  19. .onClick(() => {
  20. promptAction.showToast({ message: 'Button OK on clicked' });
  21. })
  22. .tabIndex(3) // Button-OK设置为第三个tabIndex节点

??6.1 groupDefaultFocus

我们分别将某个组件设置为tabIndex节点,设置完之后,只有当我们按下TAB/ShiftTab键在这3个组件上进行焦点切换时,才会出现快速走焦的效果。

为了解决这个问题,我们可以给每个区域的容器设置tabIndex属性。然而,这样设置存在一个问题:当首次走焦到容器上时,焦点会默认落在容器内的第一个可获焦组件上,而不是我们想要的Button1、左箭头、ButtonOK。

为了解决这个问题,我们引入了一个名为groupDefaultFocus的通用属性,该属性接受一个布尔值参数,默认值为false。使用该属性需要与tabIndex属性结合使用,首先使用tabIndex为每个区域(容器)定义焦点切换顺序,然后为Button1、左箭头、ButtonOK这些组件绑定groupDefaultFocus(true)。这样,在首次走焦到目标区域(容器)时,拥有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 }) { // 设置该Row组件为tabIndex的第一个节点
  38. Column() {
  39. Button('1').width(200).height(200)
  40. .fontSize(40)
  41. .backgroundColor('#dadbd9')
  42. .groupDefaultFocus(true) // 设置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 }) { // 设置该Row组件为第二个tabIndex节点
  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) // 设置Button-左箭头为第二个tabIndex节点的默认焦点
  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 }) { // 设置该Row组件为第三个tabIndex节点
  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) // 设置Button-OK为第三个tabIndex节点的默认焦点
  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. }

??6.2 focusOnTouch

接口:

  1. focusOnTouch(value: boolean)

点击是指使用触屏或鼠标左键进行单击,默认为false的组件,例如Button,不绑定该API时,点击Button不会使其获焦,当给Button绑定focusOnTouch(true)时,点击Button会使Button立即获得焦点。

案例:

  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. }

??6.3 focusControl.requestFocus

在任意执行语句中调用该API,指定目标组件的id为方法参数,当程序执行到该语句时,会立即给指定的目标组件申请焦点。

接口:

  1. focusControl.requestFocus(id: string)

案例:

  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. }

依次按下 TAB、A、B、C、D、E、F、N
image

??写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing??,不定期分享原创知识。
  • 更多鸿蒙最新技术知识点,请关注作者博客:https://t.doruo.cn/14DjR1rEY

image

原文链接:https://www.cnblogs.com/shudaoshan/p/18178396

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号