1. HarmomyOS 触屏事件
触屏事件指当手指/手写笔在组件上按下、滑动、抬起时触发的回调事件。包括点击事件、触摸事件和拖拽事件。
1.1. 点击事件
击事件是指通过手指或手写笔做出一次完整的按下和抬起动作。当发生点击事件时,会触发以下回调函数:单击事件是我们平时操作过程中触发的最多的事件。事件方法以“.”链式调用的方式配置系统组件支持的事件,建议每个事件方法单独写一行。
event参数提供点击事件相对于窗口或组件的坐标位置,以及发生点击的事件源。
onClick(event: (event?: ClickEvent) => void)
1.1.1. 实现步骤
实现HarmonyOS(鸿蒙)的单击事件主要分为四个步骤:
(1)定义组件,给组件分配唯一ID,之后通过ID定位组件
(2)给定义的组件绑定单击事件
(3)实现ClickedListener接口并重写onClick方法
(4)实现onClick方法中的具体逻辑,以此完成点击事件的相关业务操作
1.1.2. 实现方法
(1)使用箭头函数配置组件的事件方法。
Button('Click me')
.onClick(() => {
this.myText = 'ArkUI';
})
(2)使用匿名函数表达式配置组件的事件方法,要求使用bind,以确保函数体中的this指向当前组件。
Button('add counter')
.onClick(function(){
this.counter += 2;
}.bind(this))
(3)使用组件的成员函数配置组件的事件方法。
myClickHandler(): void {
this.counter += 2;
}
Button('add counter')
.onClick(this.myClickHandler.bind(this))
(4)使用声明的箭头函数,可以直接调用,不需要bind this
fn = () => {
console.info(`counter: ${this.counter}`)
this.counter++
}
Button('add counter')
.onClick(this.fn)
1.1.3. 完整代码
import { promptAction, router } from '@kit.ArkUI'
import { RouterParams } from '@zzsKit/zzsLib'
import { TitleBar } from '../../../components/common/TitleBar'
@Extend(Button)
function buttonItem() {
.stateEffect(true)
.type(ButtonType.Normal)
.borderRadius(8)
.fontSize(17)
.backgroundColor($r('app.color.primary_green'))
.padding({
top: 8,
bottom: 8,
left: 70,
right: 70
})
.margin({
top: 15,
bottom: 15
})
}
@Entry
@Component
struct ClickPage {
@State pageTitle: string = "点击事件"
@State text: string = '点击事件'
aboutToAppear() {
try {
this.pageTitle = (router
.getParams() as RouterParams).title
} catch (e) {
}
}
private myClick() {
promptAction.showToast(
{ message: "点击" })
this.text = "点击"
}
private myClick5(event: ClickEvent) {
promptAction.showToast(
{ message: "点击" })
this.text = "点击" + JSON.stringify(event)
}
build() {
Column() {
TitleBar({ pageTitle: $pageTitle })
Button('点击1')
.buttonItem()
.onClick((event: ClickEvent) => {
this.text = "点击1"
promptAction.showToast(
{ message: "点击1" })
})
Button('点击3')
.buttonItem()
.onClick(() => this.myClick?.())
Button('点击4')
.buttonItem()
.onClick(() => this.myClick())
Button('点击5')
.buttonItem()
.onClick((event: ClickEvent) => this.myClick5(event))
Button('点击6')
.buttonItem()
.onClick(() => {
promptAction.showToast(
{ message: "event" })
})
Button('Click').width(100).height(40)
.onClick((event?: ClickEvent) => {
if (event) {
this.text = 'Click Point:' + '\n windowX:' + event.windowX + '\n windowY:' + event.windowY
+ '\n x:' + event.x + '\n y:' + event.y + '\ntarget:' + '\n component globalPos:('
+ event.target.area.globalPosition.x + ',' + event.target.area.globalPosition.y + ')\n width:'
+ event.target.area.width + '\n height:' + event.target.area.height + '\ntimestamp' + event.timestamp;
}
})
Text(this.text).margin(15)
}
.width('100%')
.height('100%')
.alignItems(HorizontalAlign.Center)
}
}
1.2. 触摸事件
当手指或手写笔在组件上触碰时,会触发不同动作所对应的事件响应,包括按下(Down)、滑动(Move)、抬起(Up)事件:
onTouch(event: (event?: TouchEvent) => void)
(1)event.type为TouchType.Down:表示手指按下。
(2)event.type为TouchType.Up:表示手指抬起。
(3)event.type为TouchType.Move:表示手指按住移动。
(4)event.type为TouchType.Cancel:表示打断取消当前手指操作。
触摸事件可以同时多指触发,通过event参数可获取触发的手指位置、手指唯一标志、当前发生变化的手指和输入的设备源等信息。
import { router } from '@kit.ArkUI';
import { RouterParams } from '@zzsKit/zzsLib';
import { TitleBar } from '../../../components/common/TitleBar';
@Entry
@Component
struct TouchPage {
@State text: string = '';
@State eventType: string = '';
@State pageTitle: string = "点击事件"
aboutToAppear() {
try {
this.pageTitle = (router
.getParams() as RouterParams).title
} catch (e) {
}
}
build() {
Column() {
TitleBar({ pageTitle: $pageTitle })
Button('Touch').height(40).width(100)
.onTouch((event?: TouchEvent) => {
if (event) {
if (event.type === TouchType.Down) {
//按下
this.eventType = '按下';
}
if (event.type === TouchType.Up) {
//抬起
this.eventType = '抬起';
}
if (event.type === TouchType.Move) {
//移动
this.eventType = '移动';
}
this.text = '触摸类型:' + this.eventType
+ '\n触摸点与触摸之间的距离:\n'
+ 'x: ' + event.touches[0].x + '\n'
+ 'y: ' + event.touches[0].y
+ '\nComponent globalPos:('
+ event.target.area.globalPosition.x + ','
+ event.target.area.globalPosition.y + ')\n'
+ 'width:' + event.target.area.width + '\n'
+ 'height:' + event.target.area.height
}
})
Text(this.text)
}
}
}
1.3. 拖拽事件
拖拽框架提供了一种通过鼠标或手势触屏的方式传递数据,即从一个组件位置拖出数据,并拖入到另一个组件位置上进行响应,拖出一方提供数据,拖入一方接收和处理数据。该操作可以让用户方便地移动、复制或删除指定内容。
(1)拖拽操作:在某个能够响应拖出的组件上长按并滑动触发的拖拽行为,当用户释放时,拖拽操作结束;
(2)拖拽背景(背板):用户所拖动数据的形象化表示,开发者可通过onDragStart的CustomerBuilder或DragItemInfo设置,也可以通过dragPreview通用属性设置;
(3)拖拽内容:拖动的数据,使用UDMF统一API UnifiedData 进行封装;
(4)拖出对象:触发拖拽操作并提供数据的组件;
(5)拖入目标:可接收并处理拖动数据的组件;
(6)拖拽点:鼠标或手指等与屏幕的接触位置,是否进入组件范围的判定是以接触点是否进入范围进行判断。
1.3.1. 通用拖拽适配
import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData';
import { promptAction, componentSnapshot } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import { image } from '@kit.ImageKit';
@Entry
@Component
struct DragPage {
@State targetImage: string = '';
@State imageWidth: number = 100;
@State imageHeight: number = 100;
@State imgState: Visibility = Visibility.Visible;
@State pixmap: image.PixelMap|undefined = undefined
@Builder
pixelMapBuilder() {
Column() {
Image($r('app.media.startIcon'))
.width(120)
.height(120)
.backgroundColor(Color.Yellow)
}
}
getDataFromUdmfRetry(event: DragEvent, callback: (data: DragEvent) => void) {
try {
let data: UnifiedData = event.getData();
if (!data) {
return false;
}
let records: Array<unifiedDataChannel.UnifiedRecord> = data.getRecords();
if (!records || records.length <= 0) {
return false;
}
callback(event);
return true;
} catch (e) {
console.log("getData failed, code: " + (e as BusinessError).code + ", message: " + (e as BusinessError).message);
return false;
}
}
// 获取UDMF数据,首次获取失败后添加1500ms延迟重试机制
getDataFromUdmf(event: DragEvent, callback: (data: DragEvent) => void) {
if (this.getDataFromUdmfRetry(event, callback)) {
return;
}
setTimeout(() => {
this.getDataFromUdmfRetry(event, callback);
}, 1500);
}
// 调用componentSnapshot中的createFromBuilder接口截取自定义builder的截图
private getComponentSnapshot(): void {
componentSnapshot.createFromBuilder(()=>{this.pixelMapBuilder()},
(error: Error, pixmap: image.PixelMap) => {
if(error){
console.log("error: " + JSON.stringify(error))
return;
}
this.pixmap = pixmap;
})
}
// 长按50ms时提前准备自定义截图的pixmap
private PreDragChange(preDragStatus: PreDragStatus): void {
if (preDragStatus == PreDragStatus.ACTION_DETECTING_STATUS) {
this.getComponentSnapshot();
}
}
build() {
Row() {
Column() {
Text('start Drag')
.fontSize(18)
.width('100%')
.height(40)
.margin(10)
.backgroundColor('#008888')
Row() {
Image($r('app.media.app_icon'))
.width(100)
.height(100)
.draggable(true)
.margin({ left: 15 })
.visibility(this.imgState)
// 绑定平行手势,可同时触发应用自定义长按手势
.parallelGesture(LongPressGesture().onAction(() => {
promptAction.showToast({ duration: 100, message: 'Long press gesture trigger' });
}))
.onDragStart((event) => {
let data: unifiedDataChannel.Image = new unifiedDataChannel.Image();
data.imageUri = 'common/pic/img.png';
let unifiedData = new unifiedDataChannel.UnifiedData(data);
event.setData(unifiedData);
let dragItemInfo: DragItemInfo = {
pixelMap: this.pixmap,
extraInfo: "this is extraInfo",
};
return dragItemInfo;
})
// 提前准备拖拽自定义背板图
.onPreDrag((status: PreDragStatus) => {
this.PreDragChange(status);
})
.onDragEnd((event) => {
// onDragEnd里取到的result值在接收方onDrop设置
if (event.getResult() === DragResult.DRAG_SUCCESSFUL) {
promptAction.showToast({ duration: 100, message: 'Drag Success' });
} else if (event.getResult() === DragResult.DRAG_FAILED) {
promptAction.showToast({ duration: 100, message: 'Drag failed' });
}
})
}
Text('Drag Target Area')
.fontSize(20)
.width('100%')
.height(40)
.margin(10)
.backgroundColor('#008888')
Row() {
Image(this.targetImage)
.width(this.imageWidth)
.height(this.imageHeight)
.draggable(true)
.margin({ left: 15 })
.border({ color: Color.Black, width: 1 })
// 控制角标显示类型为MOVE,即不显示角标
.onDragMove((event) => {
event.setResult(DragResult.DROP_ENABLED)
event.dragBehavior = DragBehavior.MOVE
})
.allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE])
.onDrop((dragEvent?: DragEvent) => {
// 获取拖拽数据
this.getDataFromUdmf((dragEvent as DragEvent), (event: DragEvent) => {
let records: Array<unifiedDataChannel.UnifiedRecord> = event.getData().getRecords();
let rect: Rectangle = event.getPreviewRect();
this.imageWidth = Number(rect.width);
this.imageHeight = Number(rect.height);
this.targetImage = (records[0] as unifiedDataChannel.Image).imageUri;
this.imgState = Visibility.None;
// 显式设置result为successful,则将该值传递给拖出方的onDragEnd
event.setResult(DragResult.DRAG_SUCCESSFUL);
})
})
}
}
.width('100%')
.height('100%')
}
.height('100%')
}
}
1.3.2. 多选拖拽适配
import { componentSnapshot } from '@kit.ArkUI';
import { image } from '@kit.ImageKit';
@Entry
@Component
struct Drag2Page {
@State pixmap: image.PixelMap|undefined = undefined
@State numbers: number[] = []
@State isSelectedGrid: boolean[] = []
@State previewData: DragItemInfo[] = []
@State colors: Color[] = [Color.Red, Color.Blue, Color.Brown, Color.Gray, Color.Green, Color.Grey, Color.Orange,Color.Pink ,Color.Yellow]
@State numberBadge: number = 0;
@Styles
normalStyles(): void{
.opacity(1.0)
}
@Styles
selectStyles(): void{
.opacity(0.4)
}
onPageShow(): void {
let i: number = 0
for(i=0;i<100;i++){
this.numbers.push(i)
this.isSelectedGrid.push(false)
this.previewData.push({})
}
}
@Builder
RandomBuilder(idx: number) {
Column()
.backgroundColor(this.colors[idx % 9])
.width(50)
.height(50)
.opacity(1.0)
}
build() {
Column({ space: 5 }) {
Grid() {
ForEach(this.numbers, (idx: number) => {
GridItem() {
Column()
.backgroundColor(this.colors[idx % 9])
.width(50)
.height(50)
.opacity(1.0)
.id('grid'+idx)
}
.dragPreview(this.previewData[idx])
.selectable(true)
.selected(this.isSelectedGrid[idx])
// 设置多选显示效果
.stateStyles({
normal : this.normalStyles,
selected: this.selectStyles
})
.onClick(()=>{
this.isSelectedGrid[idx] = !this.isSelectedGrid[idx]
if (this.isSelectedGrid[idx]) {
this.numberBadge++;
let gridItemName = 'grid' + idx
// 选中状态下提前调用componentSnapshot中的get接口获取pixmap
componentSnapshot.get(gridItemName, (error: Error, pixmap: image.PixelMap)=>{
this.pixmap = pixmap
this.previewData[idx] = {
pixelMap:this.pixmap
}
})
} else {
this.numberBadge--;
}
})
// 使能多选拖拽,右上角数量角标需要应用设置numberBadge参数
.dragPreviewOptions({numberBadge: this.numberBadge},{isMultiSelectionEnabled:true,defaultAnimationBeforeLifting:true})
.onDragStart(()=>{
})
}, (idx: string) => idx)
}
.columnsTemplate('1fr 1fr 1fr 1fr 1fr')
.columnsGap(5)
.rowsGap(10)
.backgroundColor(0xFAEEE0)
}.width('100%').margin({ top: 5 })
}
}