OpenHarmony实战开发-使用通用事件、焦点事件

基本概念

  • 焦点

指向当前应用界面上唯一的一个可交互元素,当用户使用键盘、电视遥控器、车机摇杆/旋钮等非指向性输入设备与应用程序进行间接交互时,基于焦点的导航和交互是重要的输入手段。

  • 默认焦点

应用打开或切换页面后,若当前页上存在可获焦的组件,则树形结构的组件树中第一个可获焦的组件默认获得焦点。可以使用自定义默认焦点指定。

  • 获焦

指组件获得了焦点,同一时刻,应用中最多只有1个末端组件是获焦的,且此时它的所有祖宗组件(整个组件链)均是获焦的。当期望某个组件获焦,须确保该组件及其所有的祖宗节点均是可获焦的(focusable属性为true)。

  • 失焦

指组件从获焦状态变成了非获焦状态,失去了焦点。组件失焦时,它的所有祖宗组件(失焦组件链)与新的获焦组件链不相同的节点都会失焦。

  • 走焦

表示焦点在当前应用中转移的过程,走焦会带来原焦点组件的失焦和新焦点组件的获焦。应用中焦点发生变化的方式按行为可分为两类:

  • 主动走焦:指开发者/用户主观的行为导致焦点移动,包含:外接键盘上按下TAB/方向键、使用requestFocus主动给指定组件申请焦点、组件focusOnTouch属性为true后点击组件。
  • 被动走焦:指组件焦点因其他操作被动的转移焦点,此特性为焦点系统默认行为,无法由开发者自由设定,例如当使用if-else语句将处于获焦的组件删除/将处于获焦的组件(或其父组件)置成不可获焦时、当页面切换时。
  • 焦点态

获焦组件的样式,不同组件的焦点态样式大同小异,默认情况下焦点态不显示,仅使用外接键盘按下TAB键/方向键时才会触发焦点态样式出现。首次触发焦点态显示的TAB键/方向键不会触发走焦。当应用接收到点击事件时(包括手指触屏的按下事件和鼠标左键的按下事件),自动隐藏焦点态样式。焦点态样式由后端组件定义,开发者无法修改。

走焦规则

走焦规则是指用户使用“TAB键/SHIFT+TAB键/方向键”主动进行走焦,或焦点系统在执行被动走焦时的顺序规则。组件的走焦规则默认由走焦系统定义,由焦点所在的容器决定。

  • 线性走焦:常见的容器有Flex、Row、Column、List,这些都是典型的单方向容器,组件在这些容器内的排列都是线性的,那么走焦规则也是线性的。走焦的方向和方向键的方向一致。

图1 线性走焦示意图

在这里插入图片描述

例如Row容器,使用方向键左右(←/→)即可将焦点在相邻的2个可获焦组件之间来回切换。

  • 十字走焦:使用方向键上(↑)下(↓)左(←)右(→)可以使焦点在相邻的组件上切换。典型的是Grid容器,如下图:

图2 Grid组件十字走焦示意图
zh-cn_image_0000001511740580

说明:

TAB/SHIFT+TAB键在以上两种走焦规则上的功能和方向键一致。TAB键等同于“先执行方向键右,若无法走焦,再执行方向键下”,SHIFT+TAB键等同于“先执行方向键左,若无法走焦,再执行方向键上”。

触发走焦的按键是按下事件(DOWN事件)。

删除组件、设置组件无法获焦后,会使用线性走焦规则,自动先往被删除/Unfocusable组件的前置兄弟组件上走焦,无法走焦的话,再向后置兄弟组件上走焦。

  • tabIndex走焦:给组件设置tabIndex通用属性,自定义组件的TAB键/SHIFT+TAB键的走焦顺序。
  • 区域走焦:给容器组件设置tabIndex通用属性,再结合groupDefaultFocus通用属性,自定义容器区域的TAB键/SHIFT+TAB键的走焦顺序和默认获焦组件。
  • 走焦至容器组件规则
  • 容器组件内有可获焦子组件时。当焦点走焦到容器(该容器没有配置groupDefaultFocus)上时,若该容器组件为首次获焦,则会先计算目标容器组件的子组件的区域位置,得到距离目标容器中心点最近的子组件,焦点会走到目标容器上的该子组件上。若该容器非首次获焦,焦点会自动走焦到上一次目标容器中获焦的子组件。
  • 容器组件没有可获焦子组件时。如果该容器组件配置了onClick或是单指单击的Tap事件,焦点会走到该容器上。
  • 焦点交互:当某组件获焦时,该组件的固有点击任务或开发者绑定的onClick回调任务,会自动挂载到空格/回车按键上,当按下按键时,任务就和手指/鼠标点击一样被执行。

说明:

本文涉及到的焦点均为组件焦点,另外一个焦点的概念是:窗口焦点,指向当前获焦的窗口。当窗口失焦时,该窗口应用中的所有获焦组件全部失焦。

监听组件的焦点变化

import List from '@ohos.util.List';
import promptAction from '@ohos.promptAction';
onFocus(event: () => void)

获焦事件回调,绑定该API的组件获焦时,回调响应。

onBlur(event:() => void)

失焦事件回调,绑定该API的组件失焦时,回调响应。

onFocus和onBlur两个接口通常成对使用,来监听组件的焦点变化。

以下示例代码展示获焦/失焦回调的使用方法:

// xxx.ets
@Entry
@Component
struct FocusEventExample {@State oneButtonColor: Color = Color.Gray;@State twoButtonColor: Color = Color.Gray;@State threeButtonColor: Color = Color.Gray;build() {Column({ space: 20 }) {// 通过外接键盘的上下键可以让焦点在三个按钮间移动,按钮获焦时颜色变化,失焦时变回原背景色Button('First Button').defaultFocus(true).width(260).height(70).backgroundColor(this.oneButtonColor).fontColor(Color.Black)// 监听第一个组件的获焦事件,获焦后改变颜色.onFocus(() => {this.oneButtonColor = Color.Green;})// 监听第一个组件的失焦事件,失焦后改变颜色.onBlur(() => {this.oneButtonColor = Color.Gray;})Button('Second Button').width(260).height(70).backgroundColor(this.twoButtonColor).fontColor(Color.Black)// 监听第二个组件的获焦事件,获焦后改变颜色.onFocus(() => {this.twoButtonColor = Color.Green;})// 监听第二个组件的失焦事件,失焦后改变颜色.onBlur(() => {this.twoButtonColor = Color.Grey;})Button('Third Button').width(260).height(70).backgroundColor(this.threeButtonColor).fontColor(Color.Black)// 监听第三个组件的获焦事件,获焦后改变颜色.onFocus(() => {this.threeButtonColor = Color.Green;})// 监听第三个组件的失焦事件,失焦后改变颜色.onBlur(() => {this.threeButtonColor = Color.Gray ;})}.width('100%').margin({ top: 20 })}
}

在这里插入图片描述

上述示例包含以下4步:

  1. 应用打开时,“First Button”默认获取焦点,onFocus回调响应,背景色变成绿色。
  2. 按下TAB键(或方向键下↓),“First Button”显示焦点态样式:组件外围有一个蓝色的闭合框。不触发走焦,焦点仍然在“FirstButton”上。
  3. 按下TAB键(或方向键下↓),触发走焦,“Second Button”获焦,onFocus回调响应,背景色变成绿色;“FirstButton”失焦、onBlur回调响应,背景色变回灰色。
  4. 按下TAB键(或方向键下↓),触发走焦,“Third Button”获焦,onFocus回调响应,背景色变成绿色;“SecondButton”失焦、onBlur回调响应,背景色变回灰色。

设置组件是否获焦

通过focusable接口设置组件是否可获焦:

focusable(value: boolean)

按照组件的获焦能力可大致分为三类:

  • 默认可获焦的组件,通常是有交互行为的组件,例如Button、Checkbox,TextInput组件,此类组件无需设置任何属性,默认即可获焦。
  • 有获焦能力,但默认不可获焦的组件,典型的是Text、Image组件,此类组件缺省情况下无法获焦,若需要使其获焦,可使用通用属性focusable(true)使能。
  • 无获焦能力的组件,通常是无任何交互行为的展示类组件,例如Blank、Circle组件,此类组件即使使用focusable属性也无法使其可获焦。

说明:

focusable为false表示组件不可获焦,同样可以使组件变成不可获焦的还有通用属性enabled。

当某组件处于获焦状态时,将其的focusable属性或enabled属性设置为false,会自动使该组件失焦,然后焦点按照走焦规则将焦点转移给其他组件。

对于没有配置focusable属性,有获焦能力但默认不可获焦的组件,为其配置onClick或是单指单击的Tap手势,该组件会隐式地成为可获焦组件。如果其focusable属性被设置为false,即使配置了上述事件,该组件依然不可获焦。

表1 基础组件获焦能力
在这里插入图片描述
以下示例展示focusable接口的使用方法:

// xxx.ets
@Entry
@Component
struct FocusableExample {@State textFocusable: boolean = true;@State color1: Color = Color.Yellow;@State color2: Color = Color.Yellow;build() {Column({ space: 5 }) {Text('Default Text')    // 第一个Text组件未设置focusable属性,默认不可获焦.borderColor(this.color1).borderWidth(2).width(300).height(70).onFocus(() => {this.color1 = Color.Blue;}).onBlur(() => {this.color1 = Color.Yellow;})Divider()Text('focusable: ' + this.textFocusable)    // 第二个Text设置了focusable属性,初始值为true.borderColor(this.color2).borderWidth(2).width(300).height(70).focusable(this.textFocusable).onFocus(() => {this.color2 = Color.Blue;}).onBlur(() => {this.color2 = Color.Yellow;})Divider()Row() {Button('Button1').width(140).height(70)Button('Button2').width(160).height(70)}Divider()Button('Button3').width(300).height(70)Divider()}.width('100%').justifyContent(FlexAlign.Center).onKeyEvent((e) => {    // 绑定onKeyEvent,在该Column组件获焦时,按下'F'键,可将第二个Text的focusable置反if(e){if (e.keyCode === 2022 && e.type === KeyType.Down) {this.textFocusable = !this.textFocusable;}}})}
}

运行效果:

在这里插入图片描述
上述示例包含默认获焦和主动走焦两部分:

默认获焦:

  • 根据默认焦点的说明,该应用打开后,默认第一个可获焦元素获焦。
  • 第一个Text组件没有设置focusable(true)属性,该Text组件无法获焦。
  • 第二个Text组件的focusable属性显式设置为true,说明该组件可获焦,那么默认焦点将置到它身上。

主动走焦:

按键盘F键,触发onKeyEvent,focusable置为false,Text组件变成不可获焦,焦点自动转移。按照被动走焦中的说明项,焦点会自动从Text组件先向上寻找下一个可获焦组件,由于上一个组件是一个不可获焦的Text,所以向下寻找下一个可获焦的组件,找到并使焦点转移到Row容器上。根据走焦至容器规则,使用Tab键走焦时应该满足Z字型走焦顺序,因此焦点会自动转移到Button1上。

自定义默认焦点

defaultFocus(value: boolean)

焦点系统在页面初次构建完成时,会搜索当前页下的所有组件,找到第一个绑定了defaultFocus(true)的组件,然后将该组件置为默认焦点,若无任何组件绑定defaultFocus(true),则将第一个找到的可获焦的组件置为默认焦点。

以如下应用为例,应用布局如下:

在这里插入图片描述

以下是实现该应用的示例代码,且示例代码中没有设置defaultFocus:

// xxx.ets
import promptAction from '@ohos.promptAction';class MyDataSource implements IDataSource {private list: number[] = [];private listener: DataChangeListener | undefined = undefined;constructor(list: number[]) {this.list = list;}totalCount(): number {return this.list.length;}getData(index: number): number {return this.list[index];}registerDataChangeListener(listener: DataChangeListener): void {this.listener = listener;}unregisterDataChangeListener() {}
}class swcf {swiperController: SwiperController | undefinedfun(index: number) {if (this.swiperController) {if (index == 0) {this.swiperController.showPrevious();} else {this.swiperController.showNext();}}}
}class TmpM {left: number = 20bottom: number = 20right: number = 20
}let MarginTmp: TmpM = new TmpM()@Entry
@Component
struct SwiperExample {private swiperController: SwiperController = new SwiperController()@State tmp: promptAction.ShowToastOptions = { 'message': 'Button OK on clicked' }private data: MyDataSource = new MyDataSource([])aboutToAppear(): void {let list: number[] = []for (let i = 1; i <= 4; i++) {list.push(i);}this.data = new MyDataSource(list);}build() {Column({ space: 5 }) {Swiper(this.swiperController) {LazyForEach(this.data, (item: string) => {Row({ space: 10 }) {Column() {Button('1').width(120).height(120).fontSize(40).backgroundColor('#dadbd9')}Column({ space: 20 }) {Row({ space: 20 }) {Button('2').width(70).height(70).fontSize(40).type(ButtonType.Normal).borderRadius(20).backgroundColor('#dadbd9')Button('3').width(70).height(70).fontSize(40).type(ButtonType.Normal).borderRadius(20).backgroundColor('#dadbd9')}Row({ space: 20 }) {Button('4').width(70).height(70).fontSize(40).type(ButtonType.Normal).borderRadius(20).backgroundColor('#dadbd9')Button('5').width(70).height(70).fontSize(40).type(ButtonType.Normal).borderRadius(20).backgroundColor('#dadbd9')}Row({ space: 20 }) {Button('6').width(70).height(70).fontSize(40).type(ButtonType.Normal).borderRadius(20).backgroundColor('#dadbd9')Button('7').width(70).height(70).fontSize(40).type(ButtonType.Normal).borderRadius(20).backgroundColor('#dadbd9')}}}.width(320).height(300).justifyContent(FlexAlign.Center).borderWidth(2).borderColor(Color.Gray).backgroundColor(Color.White)}, (item: string): string => item)}.cachedCount(2).index(0).interval(4000).indicator(true).loop(true).duration(1000).itemSpace(0).curve(Curve.Linear).onChange((index: number) => {console.info(index.toString());}).margin({ left: 20, top: 20, right: 20 })Row({ space: 40 }) {Button('←').fontSize(40).fontWeight(FontWeight.Bold).fontColor(Color.Black).backgroundColor(Color.Transparent).onClick(() => {let swf = new swcf()swf.fun(0)})Button('→').fontSize(40).fontWeight(FontWeight.Bold).fontColor(Color.Black).backgroundColor(Color.Transparent).onClick(() => {let swf = new swcf()swf.fun(1)})}.width(320).height(50).justifyContent(FlexAlign.Center).borderWidth(2).borderColor(Color.Gray).backgroundColor('#f7f6dc')Row({ space: 20 }) {Button('Cancel').fontSize(30).fontColor('#787878').type(ButtonType.Normal).width(140).height(50).backgroundColor('#dadbd9')Button('OK').fontSize(30).fontColor('#787878').type(ButtonType.Normal).width(140).height(50).backgroundColor('#dadbd9').onClick(() => {promptAction.showToast(this.tmp);})}.width(320).height(80).justifyContent(FlexAlign.Center).borderWidth(2).borderColor(Color.Gray).backgroundColor('#dff2e4').margin(MarginTmp)}.backgroundColor('#f2f2f2').margin({ left: 50, top: 50, right: 50 })}
}

当前应用上无任何defaultFocus设置,所以第一个可获焦的组件默认获取焦点,按下TAB键/方向键让获焦的组件显示焦点态样式:
在这里插入图片描述

假设开发者想让应用打开的时候,无需执行多余的切换焦点操作,直接点击按键的空格/回车键,就可以执行Button-OK的onClick回调操作,那么就可以给这个Button绑定defaultFocus(true),让它成为该页面上的默认焦点:

import promptAction from '@ohos.promptAction';
@Entry
@Component
struct MouseExample {@State Tmp: promptAction.ShowToastOptions = {'message':'Button OK on clicked'}build() {Column() {Button('OK').defaultFocus(true)    // 设置Button-OK为defaultFocus.fontSize(30).fontColor('#787878').type(ButtonType.Normal).width(140).height(50).backgroundColor('#dadbd9').onClick(() => {promptAction.showToast(this.Tmp);})}}
}

在这里插入图片描述

打开应用后按TAB键,Button-OK显示了焦点态,说明默认焦点变更到了Button-OK上。然后按下空格,响应了Button-OK的onClick事件。

自定义TAB键走焦顺序

tabIndex(index: number)

tabIndex用于设置自定义TAB键走焦顺序,默认值为0。使用“TAB/Shift+TAB键”走焦时(方向键不影响),系统会自动获取到所有配置了tabIndex大于0的组件,然后按照递增/递减排序进行走焦。

以defaultFocus提供的示例为例,默认情况下的走焦顺序如下:
在这里插入图片描述

默认的走焦顺序从第一个获焦组件一路走到最后一个获焦组件,会经历Button1->Button2->Button3->Button4->Button5->Button6->Button7->左箭头->右箭头->ButtonCancel->ButtonOK。这种走焦队列比较完整,遍历了大部分的组件。但缺点是从第一个走到最后一个所经历的路径较长。

如果想实现快速的从第一个走到最后一个,又不想牺牲太多的遍历完整性,就可以使用tabIndex通用属性。

比如:开发者把白色的区域当为一个整体,黄色的区域当为一个整体,绿色的区域当为一个整体,实现Button1->左箭头->ButtonOK这种队列的走焦顺序,只需要在Button1、左箭头、ButtonOK这三个组件上依次增加tabIndex(1)、tabIndex(2)、tabIndex(3)。tabIndex的参数表示TAB走焦的顺序(从大于0的数字开始,从小到大排列)。

@Entry
@Component
struct MouseExample {build() {Column() {Button('1').width(120).height(120).fontSize(40).backgroundColor('#dadbd9').tabIndex(1)    // Button-1设置为第一个tabIndex节点}}
}
class swcf{swiperController:SwiperController|undefinedfun(){if(this.swiperController){this.swiperController.showPrevious();}}
}
@Entry
@Component
struct MouseExample {build() {Column() {Button('←').fontSize(40).fontWeight(FontWeight.Bold).fontColor(Color.Black).backgroundColor(Color.Transparent).onClick(() => {let swf = new swcf()swf.fun()}).tabIndex(2)    // Button-左箭头设置为第二个tabIndex节点}}
}
import promptAction from '@ohos.promptAction';
@Entry
@Component
struct MouseExample {@State Tmp:promptAction.ShowToastOptions = {'message':'Button OK on clicked'}build() {Column() {Button('OK').fontSize(30).fontColor('#787878').type(ButtonType.Normal).width(140).height(50).backgroundColor('#dadbd9').onClick(() => {promptAction.showToast(this.Tmp);}).tabIndex(3)    // Button-OK设置为第三个tabIndex节点}}
}

在这里插入图片描述

说明:

当焦点处于tabIndex(大于0)节点上时,TAB/ShiftTAB会优先在tabIndex(大于0)的队列中寻找后置/前置的节点,存在则走焦至相应的tabIndex节点。若不存在,则使用默认的走焦逻辑继续往后/往前走焦。

当焦点处于tabIndex(等于0)节点上时,TAB/ShiftTAB使用默认的走焦逻辑走焦,走焦的过程中会跳过tabIndex(大于0)和tabIndex(小于0)的节点。

当焦点处于tabIndex(小于0)节点上时,TAB/ShiftTAB无法走焦。

groupDefaultFocus

groupDefaultFocus(value: boolean)

自定义TAB键走焦顺序中所展示的使用tabIndex完成快速走焦的能力有如下问题:

每个区域(白色/黄色/绿色三个区域)都设置了某个组件为tabIndex节点(白色-Button1、黄色-左箭头、绿色-ButtonOK),但这样设置之后,只能在这3个组件上按TAB/ShiftTab键走焦时会有快速走焦的效果。

解决方案是给每个区域的容器设置tabIndex,但是这样设置的问题是:第一次走焦到容器上时,获焦的子组件是默认的第一个可获焦组件,并不是自己想要的组件(Button1、左箭头、ButtonOK)。

这样便引入了groupDefaultFocus通用属性,参数:boolean,默认值:false。

用法需和tabIndex组合使用,使用tabIndex给区域(容器)绑定走焦顺序,然后给Button1、左箭头、ButtonOK绑定groupDefaultFocus(true),这样在首次走焦到目标区域(容器)上时,它的绑定了groupDefaultFocus(true)的子组件同时获得焦点。

// xxx.ets
import promptAction from '@ohos.promptAction';class MyDataSource implements IDataSource {private list: number[] = [];private listener: DataChangeListener|undefined = undefined;constructor(list: number[]) {this.list = list;}totalCount(): number {return this.list.length;}getData(index: number): number {return this.list[index];}registerDataChangeListener(listener: DataChangeListener): void {this.listener = listener;}unregisterDataChangeListener() {}
}@Entry
@Component
struct SwiperExample {private swiperController: SwiperController = new SwiperController()private data: MyDataSource = new MyDataSource([])@State tmp:promptAction.ShowToastOptions = {'message':'Button OK on clicked'}aboutToAppear(): void {let list: number[] = []for (let i = 1; i <= 4; i++) {list.push(i);}this.data = new MyDataSource(list);}build() {Column({ space: 5 }) {Swiper(this.swiperController) {LazyForEach(this.data, (item: string) => {Row({ space: 10 }) {    // 设置该Row组件为tabIndex的第一个节点Column() {Button('1').width(120).height(120).fontSize(40).backgroundColor('#dadbd9').groupDefaultFocus(true)    // 设置Button-1为第一个tabIndex的默认焦点}Column({ space: 20 }) {Row({ space: 20 }) {Button('2').width(70).height(70).fontSize(40).type(ButtonType.Normal).borderRadius(20).backgroundColor('#dadbd9')Button('3').width(70).height(70).fontSize(40).type(ButtonType.Normal).borderRadius(20).backgroundColor('#dadbd9')}Row({ space: 20 }) {Button('4').width(70).height(70).fontSize(40).type(ButtonType.Normal).borderRadius(20).backgroundColor('#dadbd9')Button('5').width(70).height(70).fontSize(40).type(ButtonType.Normal).borderRadius(20).backgroundColor('#dadbd9')}Row({ space: 20 }) {Button('6').width(70).height(70).fontSize(40).type(ButtonType.Normal).borderRadius(20).backgroundColor('#dadbd9')Button('7').width(70).height(70).fontSize(40).type(ButtonType.Normal).borderRadius(20).backgroundColor('#dadbd9')}}}.width(320).height(300).justifyContent(FlexAlign.Center).borderWidth(2).borderColor(Color.Gray).backgroundColor(Color.White).tabIndex(1)}, (item:string):string => item)}.cachedCount(2).index(0).interval(4000).indicator(true).loop(true).duration(1000).itemSpace(0).curve(Curve.Linear).onChange((index: number) => {console.info(index.toString());}).margin({ left: 20, top: 20, right: 20 })Row({ space: 40 }) {    // 设置该Row组件为第二个tabIndex节点Button('←').fontSize(40).fontWeight(FontWeight.Bold).fontColor(Color.Black).backgroundColor(Color.Transparent).onClick(() => {this.swiperController.showPrevious();}).groupDefaultFocus(true)    // 设置Button-左箭头为第二个tabIndex节点的默认焦点Button('→').fontSize(40).fontWeight(FontWeight.Bold).fontColor(Color.Black).backgroundColor(Color.Transparent).onClick(() => {this.swiperController.showNext();})}.width(320).height(50).justifyContent(FlexAlign.Center).borderWidth(2).borderColor(Color.Gray).backgroundColor('#f7f6dc').tabIndex(2)Row({ space: 20 }) {    // 设置该Row组件为第三个tabIndex节点Button('Cancel').fontSize(30).fontColor('#787878').type(ButtonType.Normal).width(140).height(50).backgroundColor('#dadbd9')Button('OK').fontSize(30).fontColor('#787878').type(ButtonType.Normal).width(140).height(50).backgroundColor('#dadbd9').defaultFocus(true).onClick(() => {promptAction.showToast(this.tmp);}).groupDefaultFocus(true)    // 设置Button-OK为第三个tabIndex节点的默认焦点}.width(320).height(80).justifyContent(FlexAlign.Center).borderWidth(2).borderColor(Color.Gray).backgroundColor('#dff2e4').margin({ left: 20, bottom: 20, right: 20 }).tabIndex(3)}.backgroundColor('#f2f2f2').margin({ left: 50, top: 50, right: 50 })}
}

在这里插入图片描述

focusOnTouch

focusOnTouch(value: boolean)

点击获焦能力,参数:boolean,默认值:false(输入类组件:TextInput、TextArea、Search、Web默认值是true)。

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

给容器绑定focusOnTouch(true)时,点击容器区域,会立即使容器的第一个可获焦组件获得焦点。

示例代码:

// requestFocus.ets
@Entry
@Component
struct RequestFocusExample {@State idList: string[] = ['A', 'B', 'C', 'D', 'E', 'F', 'N']build() {Column({ space:20 }){Button("id: " + this.idList[0] + " focusOnTouch(true) + focusable(false)").width(400).height(70).fontColor(Color.White).focusOnTouch(true).hoverEffect(HoverEffect.Scale).focusable(false)Button("id: " + this.idList[1] + " default").width(400).height(70).fontColor(Color.White).hoverEffect(HoverEffect.Scale)Button("id: " + this.idList[2] + " focusOnTouch(false)").width(400).height(70).fontColor(Color.White).focusOnTouch(false).hoverEffect(HoverEffect.Scale)Button("id: " + this.idList[3] + " focusOnTouch(true)").width(400).height(70).fontColor(Color.White).focusOnTouch(true).hoverEffect(HoverEffect.Scale)}.width('100%').margin({ top:20 })}
}

在这里插入图片描述

解读:

Button-A虽然设置了focusOnTouch(true),但是同时也设置了focusable(false),该组件无法获焦,因此点击后也无法获焦;

Button-B不设置相关属性,点击后不会获焦;

Button-C设置了focusOnTouch(false),同Button-B,点击后也不会获焦;

Button-D设置了focusOnTouch(true),点击即可使其获焦;

说明:

焦点态在屏幕接收点击事件后会立即清除,因此该示例代码在每次点击后,需要再次按下TAB键使焦点态再次显示,才可知道当前焦点所在的组件。

focusControl.requestFocus

focusControl.requestFocus(id: string)

主动申请焦点能力的全局方法,参数:string,参数表示被申请组件的id(通用属性id设置的字符串)。

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

代码示例:

// requestFocus.ets
import promptAction from '@ohos.promptAction';@Entry
@Component
struct RequestFocusExample {@State idList: string[] = ['A', 'B', 'C', 'D', 'E', 'F', 'N']@State requestId: number = 0build() {Column({ space:20 }){Row({space: 5}) {Button("id: " + this.idList[0] + " focusable(false)").width(200).height(70).fontColor(Color.White).id(this.idList[0]).focusable(false)Button("id: " + this.idList[1]).width(200).height(70).fontColor(Color.White).id(this.idList[1])}Row({space: 5}) {Button("id: " + this.idList[2]).width(200).height(70).fontColor(Color.White).id(this.idList[2])Button("id: " + this.idList[3]).width(200).height(70).fontColor(Color.White).id(this.idList[3])}Row({space: 5}) {Button("id: " + this.idList[4]).width(200).height(70).fontColor(Color.White).id(this.idList[4])Button("id: " + this.idList[5]).width(200).height(70).fontColor(Color.White).id(this.idList[5])}}.width('100%').margin({ top:20 }).onKeyEvent((e) => {if(e){if (e.keyCode >= 2017 && e.keyCode <= 2022) {this.requestId = e.keyCode - 2017;} else if (e.keyCode === 2030) {this.requestId = 6;} else {return;}if (e.type !== KeyType.Down) {return;}}let res = focusControl.requestFocus(this.idList[this.requestId]);let tmps:promptAction.ShowToastOptions = {'message':'Request success'}let tmpf:promptAction.ShowToastOptions = {'message':'Request failed'}if (res) {promptAction.showToast(tmps);} else {promptAction.showToast(tmpf);}})}
}

在这里插入图片描述

解读:页面中共6个Button组件,其中Button-A组件设置了focusable(false),表示其不可获焦,在外部容器的onKeyEvent中,监听按键事件,当按下A ~ F按键时,分别去申请Button A ~ F 的焦点,另外按下N键,是给当前页面上不存在的id的组件去申请焦点。

  1. 按下TAB键,由于第一个组件Button-A设置了无法获焦,那么默认第二个组件Button-B获焦,Button-B展示焦点态样式;
  2. 键盘上按下A键,申请Button-A的焦点,气泡显示Request failed,表示无法获取到焦点,焦点位置未改变;
  3. 键盘上按下B键,申请Button-B的焦点,气泡显示Requestsuccess,表示获焦到了焦点,焦点位置原本就在Button-B,位置未改变;
  4. 键盘上按下C键,申请Button-C的焦点,气泡显示Requestsuccess,表示获焦到了焦点,焦点位置从Button-B变更为Button-C;
  5. 键盘上按下D键,申请Button-D的焦点,气泡显示Requestsuccess,表示获焦到了焦点,焦点位置从Button-C变更为Button-D;
  6. 键盘上按下E键,申请Button-E的焦点,气泡显示Requestsuccess,表示获焦到了焦点,焦点位置从Button-D变更为Button-E;
  7. 键盘上按下F键,申请Button-F的焦点,气泡显示Requestsuccess,表示获焦到了焦点,焦点位置从Button-E变更为Button-F;
  8. 键盘上按下N键,申请未知组件的焦点,气泡显示Request failed,表示无法获取到焦点,焦点位置不变;

如果大家还没有掌握鸿蒙,现在想要在最短的时间里吃透它,我这边特意整理了《鸿蒙语法ArkTS、TypeScript、ArkUI等…视频教程》以及《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

鸿蒙语法ArkTS、TypeScript、ArkUI等…视频教程:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

在这里插入图片描述

OpenHarmony APP开发教程步骤:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

在这里插入图片描述

《鸿蒙开发学习手册》:

如何快速入门:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

1.基本概念
2.构建第一个ArkTS应用
3.……

在这里插入图片描述

开发基础知识:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

在这里插入图片描述

基于ArkTS 开发:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

在这里插入图片描述

鸿蒙生态应用开发白皮书V2.0PDF:https://docs.qq.com/doc/DZVVkRGRUd3pHSnFG

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/830048.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

工业异常检测

工业异常检测在业界和学界都一直是热门&#xff0c;近期其更是迎来了全新突破&#xff1a;与大模型相结合&#xff01;让异常检测变得更快更准更简单&#xff01; 比如模型AnomalyGPT&#xff0c;它克服了以往的局限&#xff0c;能够让大模型充分理解工业场景图像&#xff0c;判…

SpringMVC基础篇(四)

文章目录 1.视图1.基本介绍1.视图介绍2.为什么需要自定义视图 2.自定义视图实例1.思路分析2.代码实例1.view.jsp2.接口3.配置自定义视图解析器springDispatcherServlet-servlet.xml4.自定义视图MyView.java5.view_result.jsp6.结果展示 3.自定义视图执行流程4.自定义视图执行流…

比较美观即将跳转html源码

源码介绍 比较美观即将跳转html源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面 源码截图 比较美观的一个跳转界面&#xff0c;修改方法如上&…

Vitis HLS 学习笔记--AXI4 主接口

目录 1. 简介 2. 认识MAXI 3. MAXI突发操作 3.1 全局/本地存储器 3.2 MAXI优势与特点 3.3 查看MAXI报告 3.3.1 HW Interfaces 3.3.2 M_AXI Burst Information 3.4 MAXI 资源消耗 4. 理解 Volatile 4.1 标准C/C中的 volatile 4.2 HLS 中的 volatile 5. 总结 1. 简介…

idea中使用GlassFish服务器启动项目

idea中使用GlassFish服务器进行测试 1.项目背景 当前在研究openMDM项目, 不过该项目不是springboot项目, 并且是使用GlassFish进行war部署的, 但是需要在idea中进行项目的二次开发,故需要进行idea启动项目并且进行开发和调试 2.GlassFish是什么 GlassFish是一个web服务器, …

ROS学习笔记(14)拉普拉斯变换和PID

0.前提 近些时间在对睿抗的ROS仿真赛进行小组安排&#xff0c;对小组成员进行了一些安排&#xff0c;也要求他们以本次比赛写下自己的比赛经历博客&#xff0c;他们的培训由我来安排和负责&#xff0c;因此我得加吧油&#xff0c;起码保证我的进度得快过他们&#xff0c;才能安…

图像处理技术与应用(三)

图像处理技术与应用入门 图像信息 切片 from skimage import io # 使用 io.imread() 函数来读取图像 img io.imread(cc.jpg) ] roi img[100:5000, 500:1780] # 显示ROI区域 io.imshow(roi) io.show() # 显示图像 红色文字段定义感兴趣的区域&#xff08;ROI&#xff09…

ArcGIS基础:便捷分享图层包和地图包

1、分享图层包&#xff1a; 首先&#xff0c;选中要分享的数据&#xff0c;右键创建图层包&#xff0c;修改保存路径。 找到项目描述那一栏&#xff0c;将摘要、标签、描述都填写分享图层包的相关内容。 一切设置好之后&#xff0c;点击右上角的【分析】按钮。 点击分析之后…

【PyTorch与深度学习】2、PyTorch张量的运算API(上)

课程地址 最近做实验发现自己还是基础框架上掌握得不好&#xff0c;于是开始重学一遍PyTorch框架&#xff0c;这个是课程笔记&#xff0c;这个课还是讲的简略&#xff0c;我半小时的课听了一个半小时。 1. 张量 1.1 张量操作 &#xff08;1&#xff09;chunk&#xff1a;将一…

蓝桥杯ctf2024 部分wp

数据分析 1. packet 密码破解 1. cc 逆向分析 1. 欢乐时光 XXTEA #include<stdio.h> #include<stdint.h> #define DELTA 0x9e3779b9 #define MX (((z>>5^y<<2)(y>>3^z<<4))^((sum^y)(key[(p&3)^e]^z))) void btea(unsigned int* v…

Linux软件包管理器——yum

文章目录 1.什么是软件包1.1安装与删除命令1.2注意事项1.3查看软件包1.3.1注意事项&#xff1a; 2.关于rzsz3.有趣的Linux下的指令 -sl 1.什么是软件包 在Linux下安装软件, 一个通常的办法是下载到程序的源代码, 并进行编译, 得到可执行程序. 但是这样太麻烦了, 于是有些人把一…

335GB,台北地区倾斜摄影OSGB数据V0.2版介绍!

前几天发布了台北地区倾斜摄影OSGB数据第一个版本(139GB,台北倾斜摄影OSGB数据V0.1版),虽然数据还是一个半成品&#xff0c;完全没想到热度很高&#xff0c;很多读者对这份数据都有比较浓厚的兴趣&#xff0c;在这里首先感谢各位读者的大力支持与鼓励&#xff0c;给了我持续更新…

单路双电源三态控制的电平转换总线收发器AiP74LVC1T45

AiP74LVC1T45框图 AiP74LVC1T45引脚定义 AiP74LVC1T45丝印 概述 AiP74LVCH1T45是一个双电源带三态控制的总线收发器&#xff0c;具有3状态输出&#xff0c;可实现双向电平转换。它们具有两个1位输入输出端口(A和B)&#xff0c;一个方向控制输入(DIR)和双电源引脚(VCC(A)和VCC(…

C语言 | Leetcode C语言题解之第55题跳跃游戏

题目&#xff1a; 题解&#xff1a; #define max(a, b) (((a) > (b)) ? (a) : (b))bool canJump(int* nums, int numsSize){int cover 0;int i;// 只可能获取cover范围中的步数&#xff0c;所以i<coverfor(i 0; i < cover; i) {// 更新cover为从i出发能到达的最大…

idea No versioned directories to update were found

idea如何配置svn以及svn安装时需要注意什么 下载地址&#xff1a;https://112-28-188-82.pd1.123pan.cn:30443/download-cdn.123pan.cn/batch-download/123-820/3ec9445a/1626635-0/3ec9445a25ba365a23fc433ce0c16f34?v5&t1714358478&s171435847804276f7d9249382ba512…

Linux:浏览器访问网站的基本流程(优先级从先到后)

浏览器访问网站的基本流程&#xff08;优先级从先到后&#xff09; 首先查找浏览器是否存在该网站的访问缓存 其次查找本机的域名解析服务器 windows&#xff1a;C:\Windows\System32\drivers\etc\hostsLinux&#xff1a;/etc/hosts 使用外部的域名解析服务器解析&#xff…

LMDeploy 量化部署 LLM-VLM 实践 学习笔记

视频链接 https://www.bilibili.com/video/BV1tr421x75B/?vd_sourcea1ce254b4a97f9f687a83e661793cb2c 什么是模型部署 部署指的是已经开发好的大模型投入使用&#xff0c;要把模型部署到服务器或者移动端里&#xff0c;如何在有限的资源里加载大模型&#xff1f; 比如你好不…

网络安全 SQLmap-tamper的使用

目录 使用SQLmap Tamper脚本 1. 选择合适的Tamper脚本 2. 在命令行中使用Tamper脚本 3. 组合使用Tamper脚本 4. 注意和考虑 黑客零基础入门学习路线&规划 网络安全学习路线&学习资源 SQLmap是一款强大的自动化SQL注入和数据库取证工具。它用于检测和利用SQL注入漏…

计算机网络之传输层TCP\UDP协议

UDP协议 用户数据报协议UDP概述 UDP只在IP数据报服务之上增加了很少功能&#xff0c;即复用分用和差错检测功能 UDP的主要特点&#xff1a; UDP是无连接的&#xff0c;减少开销和发送数据之前的时延 UDP使用最大努力交付&#xff0c;即不保证可靠交付&#xff0c;可靠性由U…

关于google search console工具提交sitemap.xml无法抓取的问题解决办法

其实这个问题很好解决。 第一种情况&#xff1a;利用工具为我们的网站自动生成静态的sitemap.xml文件。这种可以检查下是否完整&#xff0c;然后上传到根目录下去&#xff0c;再去google search console提交我们的网站地图。 第二种情况&#xff1a;同样利用工具自动生成动态s…