触屏事件
触屏事件指当手指/手写笔在组件上按下、滑动、抬起时触发的回调事件。包括点击事件、拖拽事件和触摸事件。
**图1 **触摸事件原理
点击事件
点击事件是指通过手指或手写笔做出一次完整的按下和抬起动作。当发生点击事件时,会触发以下回调函数:
onClick(event: (event?: ClickEvent) => void)
event参数提供点击事件相对于窗口或组件的坐标位置,以及发生点击的事件源。
例如通过按钮的点击事件控制图片的显示和隐藏。
@Entry
@Component
struct IfElseTransition {
@State flag: boolean = true;
@State btnMsg: string = 'show';
build() {
Column() {
Button(this.btnMsg).width(80).height(30).margin(30)
.onClick(() => {
if (this.flag) {
this.btnMsg = 'hide';
} else {
this.btnMsg = 'show';
}
// 点击Button控制Image的显示和消失
this.flag = !this.flag;
})
if (this.flag) {
Image($r('app.media.icon')).width(200).height(200)
}
}.height('100%').width('100%')
}
}
拖拽事件
拖拽事件指手指/手写笔长按组件(>=500ms),并拖拽到接收区域释放的事件。拖拽事件触发流程:
拖拽事件的触发通过长按、拖动平移判定,手指平移的距离达到5vp即可触发拖拽事件。ArkUI支持应用内、跨应用的拖拽事件。
如下是跨窗口拖拽,拖出窗口示例:
import image from '@ohos.multimedia.image';
@Entry
@Component
struct Index {
@State visible: Visibility = Visibility.Visible
private pixelMapReader = undefined
aboutToAppear() {
console.info('begin to create pixmap has info message: ')
this.createPixelMap()
}
createPixelMap() {
let color = new ArrayBuffer(4 * 96 * 96);
var buffer = new Uint8Array(color);
for (var i = 0; i < buffer.length; i++) {
buffer[i] = (i + 1) % 255;
}
let opts = {
alphaType: 0,
editable: true,
pixelFormat: 4,
scaleMode: 1,
size: { height: 96, width: 96 }
}
const promise = image.createPixelMap(color, opts);
promise.then((data) => {
console.info('create pixmap has info message: ' + JSON.stringify(data))
this.pixelMapReader = data;
})
}
@Builder pixelMapBuilder() {
Text('drag item')
.width('100%')
.height(100)
.fontSize(16)
.textAlign(TextAlign.Center)
.borderRadius(10)
.backgroundColor(0xFFFFFF)
}
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text('App1')
.width('40%')
.height(80)
.fontSize(20)
.margin(30)
.textAlign(TextAlign.Center)
.backgroundColor(Color.Pink)
.visibility(Visibility.Visible)
Text('Across Window Drag This')
.width('80%')
.height(80)
.fontSize(16)
.margin(30)
.textAlign(TextAlign.Center)
.backgroundColor(Color.Pink)
.visibility(this.visible)
.onDragStart(() => { //启动跨窗口拖拽
console.info('Text onDrag start')
return { pixelMap: this.pixelMapReader, extraInfo: 'custom extra info.' }
})
.onDrop((event: DragEvent, extraParams: string) => {
console.info('Text onDragDrop, ')
this.visible = Visibility.None //拖动结束后,使源不可见
})
}
.width('100%')
.height('100%')
}
}
跨窗口拖拽,拖入示例:
@Entry
@Component
struct Index {
@State number: string[] = ['drag here']
@State text: string = ''
@State bool1: boolean = false
@State bool2: boolean = false
@State visible: Visibility = Visibility.Visible
@State visible2: Visibility = Visibility.None
scroller: Scroller = new Scroller()
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text('App2')
.width('40%')
.height(80)
.fontSize(20)
.margin(30)
.textAlign(TextAlign.Center)
.backgroundColor(Color.Pink)
.visibility(Visibility.Visible)
List({ space: 20, initialIndex: 0 }) {
ForEach(this.number, (item) => {
ListItem() {
Text('' + item)
.width('100%')
.height(80)
.fontSize(16)
.borderRadius(10)
.textAlign(TextAlign.Center)
.backgroundColor(0xFFFFFF)
}
}, item => item)
ListItem() {
Text('Across Window Drag This')
.width('80%')
.height(80)
.fontSize(16)
.margin(30)
.textAlign(TextAlign.Center)
.backgroundColor(Color.Pink)
.visibility(this.visible2)
}
}
.height('50%')
.width('90%')
.border({ width: 1 })
.divider({ strokeWidth: 2, color: 0xFFFFFF, startMargin: 20, endMargin: 20 })
.onDragEnter((event: DragEvent, extraParams: string) => { //拖拽进去组件
console.info('List onDragEnter, ' + extraParams)
})
.onDragMove((event: DragEvent, extraParams: string) => { //拖拽时移动
console.info('List onDragMove, ' + extraParams)
})
.onDragLeave((event: DragEvent, extraParams: string) => { //拖拽离开组件
console.info('List onDragLeave, ' + extraParams)
})
.onDrop((event: DragEvent, extraParams: string) => { //释放组件
console.info('List onDragDrop, ' + extraParams)
this.visible2 = Visibility.Visible //拖拽完成使拖入目标可见
})
}
.width('100%')
.height('100%')
}
}
触摸事件
当手指或手写笔在组件上触碰时,会触发不同动作所对应的事件响应,包括按下(Down)、滑动(Move)、抬起(Up)事件:
onTouch(event: (event?: TouchEvent) => void)
event.type为TouchType.Down:表示手指按下。
event.type为TouchType.Up:表示手指抬起。
event.type为TouchType.Move:表示手指按住移动。
触摸事件可以同时多指触发,通过event参数可获取触发的手指位置、手指唯一标志、当前发生变化的手指和输入的设备源等信息。
// xxx.ets
@Entry
@Component
struct TouchExample {
@State text: string = '';
@State eventType: string = '';
build() {
Column() {
Button('Touch').height(40).width(100)
.onTouch((event: TouchEvent) => {
if (event.type === TouchType.Down) {
this.eventType = 'Down';
}
if (event.type === TouchType.Up) {
this.eventType = 'Up';
}
if (event.type === TouchType.Move) {
this.eventType = 'Move';
}
this.text = 'TouchType:' + this.eventType + '\nDistance between touch point and touch element:\nx: '
+ event.touches[0].x + '\n' + 'y: ' + event.touches[0].y + '\nComponent globalPos:('
+ event.target.area.globalPosition.x + ',' + event.target.area.globalPosition.y + ')\nwidth:'
+ event.target.area.width + '\nheight:' + event.target.area.height
})
Button('Touch').height(50).width(200).margin(20)
.onTouch((event: TouchEvent) => {
if (event.type === TouchType.Down) {
this.eventType = 'Down';
}
if (event.type === TouchType.Up) {
this.eventType = 'Up';
}
if (event.type === TouchType.Move) {
this.eventType = 'Move';
}
this.text = 'TouchType:' + this.eventType + '\nDistance between touch point and touch element:\nx: '
+ event.touches[0].x + '\n' + 'y: ' + event.touches[0].y + '\nComponent globalPos:('
+ event.target.area.globalPosition.x + ',' + event.target.area.globalPosition.y + ')\nwidth:'
+ event.target.area.width + '\nheight:' + event.target.area.height
})
Text(this.text)
}.width('100%').padding(30)
}
}
焦点事件
-
焦点
指向当前应用界面上唯一的一个可交互元素,当用户使用键盘、电视遥控器、车机摇杆/旋钮等非指向性输入设备与应用程序进行间接交互时,基于焦点的导航和交互是重要的输入手段。
-
默认焦点
应用打开或切换页面后,若当前页上存在可获焦的组件,则树形结构的组件树中第一个可获焦的组件默认获得焦点。可以使用自定义默认焦点进行自定义指定。
-
获焦
指组件获得了焦点,同一时刻,应用中最多只有1个末端组件是获焦的,且此时它的所有祖宗组件(整个组件链)均是获焦的。当期望某个组件获焦,须确保该组件及其所有的祖宗节点均是可获焦的(focusable属性为true)。
-
失焦
指组件从获焦状态变成了非获焦状态,失去了焦点。组件失焦时,它的所有祖宗组件(失焦组件链)与新的获焦组件链不相同的节点都会失焦。
-
走焦
表示焦点在当前应用中转移的过程,走焦会带来原焦点组件的失焦和新焦点组件的获焦。应用中焦点发生变化的方式按行为可分为两类:
- 主动走焦:指开发者/用户主观的行为导致焦点移动,包含:外接键盘上按下TAB/方向键、使用requestFocus主动给指定组件申请焦点、组件focusOnTouch属性为true后点击组件。
- 被动走焦:指组件焦点因其他操作被动的转移焦点,此特性为焦点系统默认行为,无法由开发者自由设定,例如当使用if-else语句将处于获焦的组件删除/将处于获焦的组件(或其父组件)置成不可获焦时、当页面切换时。
-
焦点态
获焦组件的样式,不同组件的焦点态样式大同小异,默认情况下焦点态不显示,仅使用外接键盘按下TAB键/方向键时才会触发焦点态样式出现。首次触发焦点态显示的TAB键/方向键不会触发走焦。当应用接收到点击事件时(包括手指触屏的按下事件和鼠标左键的按下事件),自动隐藏焦点态样式。焦点态样式由后端组件定义,开发者无法修改。
走焦规则
走焦规则是指用户使用“TAB键/SHIFT+TAB键/方向键”主动进行走焦,或焦点系统在执行被动走焦时的顺序规则。组件的走焦规则默认由走焦系统定义,由焦点所在的容器决定。
-
线性走焦:常见的容器有Flex、Row、Column、List,这些都是典型的单方向容器,组件在这些容器内的排列都是线性的,那么走焦规则也是线性的。走焦的方向和方向键的方向一致。
**图1 **线性走焦示意图
监听组件的焦点变化
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')
.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 })
}
}
以下示例为大家展示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.keyCode === 2022 && e.type === KeyType.Down) {
this.textFocusable = !this.textFocusable;
}
})
}
}