概述
拖拽操作是一种直观且高效的数据传输方式,它允许用户通过标准手势(包括用手指、鼠标或触控笔按住并移动)在应用程序之间及内部进行数据传输。
拖拽功能不仅操作便捷,还能与多种系统能力深度融合,拓展出更为广泛的应用场景。例如,跨设备拖拽让用户能在不同设备间无缝传输数据,跨窗口拖拽提升了多任务处理的灵活性。此外,基于拖拽操作还可以开发出更多创新性的应用场景,如AI智能识别、水印添加等,这些创新性的功能接入统称为“统一拖拽”。
本文将介绍几种典型拖拽场景及其具体实现方案,帮助开发者更好地理解和应用拖拽技术。
通过设置组件的拖拽响应,可以自定义拖出数据、拖入数据和拖拽背板图,实现如下场景:
拖拽图像增加水印:为拖拽的图像添加水印,水印内容为图像的拖拽时间。开发者可以在应用时根据需求自定义水印内容,例如标记拖拽图片的来源信息,为图像管理与溯源提供便利。
自定义拖拽背板图:将拖拽中的背板图设置为自定义数据内容。开发者可根据个性化需求打造独特的拖拽视觉效果。
AI识别拖拽内容:通过在接收拖拽内容时增加AI识别功能,使得只能显示文字的组件可以接收图片拖拽并显示图片中的文字信息。开发者可以将此能力应用于拖拽识图搜索。
将拖拽框架与系统的分屏能力、键鼠穿越能力、小艺及中转站结合,可以实现如下场景:
分屏拖拽:演示了分屏拖拽的功能,可以在分屏中打开两个不同的应用,实现跨应用拖拽。
跨设备拖拽:演示了基于键鼠穿越能力的跨设备拖拽,可以在平板和2in1设备中使用此功能以直观便捷地交换数据。
拖入小艺和中转站:演示了小艺和中转站与拖拽框架结合的能力,可以利用中转站暂存拖拽内容或进行跨设备拖拽,也可以利用小艺的AI对话式分析能力处理拖拽内容。
实现原理
拖拽流程可以分为三部分:发起拖拽、拖拽中和释放拖拽。其中,拖出方通过draggable()和onDragStart()等接口处理拖出数据,拖入方通过allowDrop()和onDrop()等接口处理拖入数据,拖拽数据使用UDMF统一数据对象UnifiedData 进行封装。下面,将按照这三个部分依次介绍拖拽的基础实现。
| 发起拖拽 | 拖拽中 | 释放拖拽 |
![]() |
![]() |
![]() |
表1 拖拽流程展示
发起拖拽
默认支持拖出能力的组件,如Search、Hyperlink等,在拖出时会使用组件的默认拖出响应。其中Search组件默认拖拽内容为选中的文字,Hyperlink组件默认拖拽内容为超链接地址。如果想自定义组件的拖拽内容,需要在组件的onDragStart()接口中将自定义数据封装成UnifiedData数据对象,通过DragEvent的setData()接口设置拖出数据。对于其他非默认组件或自定义组件,如果想实现其拖出功能,需要将组件的draggable()属性设置为true,并自定义组件的拖拽内容。以Text组件为例,示例代码如下:
Text('自定义拖出响应,拖拽video')
.draggable(true)
.onDragStart((event) =>{
// 处理拖出数据
letvideo: unifiedDataChannel.Video=newunifiedDataChannel.Video();
video.videoUri='/resources/rawfile/01.mp4';
letdata: unifiedDataChannel.UnifiedData=newunifiedDataChannel.UnifiedData(video);
(eventasDragEvent).setData(data);
})
可以在onDragStart()中自由地处理拖拽信息,例如为图片添加水印,详情见拖拽图像增加水印。
拖拽中
通过标准手势发起拖拽后,系统会默认将组件本身的截图作为拖拽移动中的背板图。如果想自定义拖拽背板图,需要在组件的onDragStart()接口中通过回调的CustomBuilder或DragItemInfo进行设置。以Text组件为例,示例代码如下:
Text('自定义拖拽背板图')
.draggable(true)
.onDragStart(() =>{
// 返回自定义背板图
letdragItemInfo:DragItemInfo= {
pixelMap:this.pixelMap,
builder:() =>{this.pixelMapBuilder() },
extraInfo:"this is extraInfo",
};
returndragItemInfo;
})
可以将拖拽背板图设置为自定义的图片或者文字,详情见自定义拖拽背板图。
释放拖拽
默认支持拖入能力的组件,如Search等,将目标拖入组件区域内会使用默认拖入响应。如果想自定义组件的拖入响应,需要将组件的allowDrop()属性设置为允许拖入的数据类型,并在其onDrop()接口中通过DragEvent的getData()接口获取拖入数据后,对数据内容进行相应处理。
Text(this.targetText)
.allowDrop([uniformTypeDescriptor.UniformDataType.PLAIN_TEXT])
.onDrop((event: DragEvent) =>{
// 处理拖入数据
letrecords:Array = event.getData().getRecords();
letplainText: unifiedDataChannel.PlainText= records[0]asunifiedDataChannel.PlainText;
this.targetText= plainText.textContent;
})
可以在onDrop()中处理接收到的数据,例如将图片识别为文字以显示在只支持文字的组件上,详情见AI识别拖拽内容。
拖拽图像增加水印
在拖拽过程中,可以自定义拖出响应,为拖拽图像增加水印,以标识图像的相关信息。下面以在图像中增加拖拽时间水印为例,介绍实现原理。
实现原理
在拖出对象的onDragStart()接口中获取图像信息,调用系统绘制能力drawing在图像上绘制水印,通过DragEvent的setData()接口将水印图像设置为拖拽数据。
开发步骤
1. 将Image的draggable()属性设置为true。
// src/main/ets/pages/watermark/Watermark.ets
Image($rawfile('river.png'))
// ...
.draggable(true)
2. 在拖出对象的onDragStart()接口中,获取图像信息并将其转换成PixelMap。
.onDragStart((event: DragEvent) =>{
constresourceMgr: resourceManager.ResourceManager=this.context.resourceManager;
letrawFileDescriptor = resourceMgr.getRawFdSync('river.png');
constimageSourceApi: image.ImageSource= image.createImageSource(rawFileDescriptor);
letpixelMap = imageSourceApi.createPixelMapSync();
// ...
})
3. 将图片绘制到Canvas画布上,并获取拖拽时间作为水印绘制到画布上的指定位置,得到添加水印的图像。
// 获取拖拽时间 this.time=this.getTimeWatermark(systemDateTime.getTime(false)); letmarkPixelMap =this.addWaterMark(this.time, pixelMap); // 绘制水印 addWaterMark(watermark:string, pixelMap: image.PixelMap) { if(canIUse('SystemCapability.Graphics.Drawing')) { watermark =this.context.resourceManager.getStringSync($r('app.string.drag_time')) + watermark; letimageWidth = pixelMap.getImageInfoSync().size.width; letimageHeight = pixelMap.getImageInfoSync().size.height; letimageScale = imageWidth / display.getDefaultDisplaySync().width; constcanvas =newdrawing.Canvas(pixelMap); constpen =newdrawing.Pen(); constbrush =newdrawing.Brush(); pen.setColor({ alpha:102, red:255, green:255, blue:255 }) brush.setColor({ alpha:102, red:255, green:255, blue:255 }) constfont =newdrawing.Font(); font.setSize(48* imageScale); lettextWidth = font.measureText(watermark, drawing.TextEncoding.TEXT_ENCODING_UTF8); consttextBlob = drawing.TextBlob.makeFromString(watermark, font, drawing.TextEncoding.TEXT_ENCODING_UTF8); canvas.attachBrush(brush); canvas.attachPen(pen); canvas.drawTextBlob(textBlob, imageWidth -24* imageScale - textWidth, imageHeight -32* imageScale); canvas.detachBrush(); canvas.detachPen(); }else{ hilog.info(0x0000,TAG,'watermark is not supported'); } returnpixelMap; }
4. 将图像打包保存在文件中,调用DragEvent的setData()接口将水印图像设置为拖拽数据。
letpackOpts: image.PackingOption= {format:'image/png',quality:20};
letfile =
fs.openSync(`${this.context.filesDir}/watermark.png`, fs.OpenMode.CREATE| fs.OpenMode.READ_WRITE);
constimagePackerApi: image.ImagePacker= image.createImagePacker();
imagePackerApi.packToFile(markPixelMap, file.fd, packOpts);
letimg: unifiedDataChannel.Image=newunifiedDataChannel.Image();
img.imageUri= fileUri.getUriFromPath(`${this.context.filesDir}/watermark.png`);
letdata: unifiedDataChannel.UnifiedData=newunifiedDataChannel.UnifiedData(img);
(eventasDragEvent).setData(data);
fs.closeSync(file.fd);
自定义拖拽背板图
在拖拽过程中,可以自定义拖拽背板图,展示拖拽数据的相关信息。
实现原理
在拖出对象的onDragStart()接口中,回调自定义的PixelMap作为拖拽中的背板图。
开发步骤
1. 创建自定义组件。
// src/main/ets/pages/background/Background.ets
@Builder
pixelMapBuilder() {
Column() {
Text($r('app.string.background_content'))
.fontSize('16fp')
.fontColor(Color.Black)
.margin({
left:'16vp',
right:'16vp',
top:'8vp',
bottom:'8vp'
})
}
.backgroundColor(Color.White)
.borderRadius(18)
}
2. 将自定义组件转换成PixelMap,作为拖拽过程中显示的图片。
说明:由于CustomBuilder需要离线渲染之后才能使用,存在一定的性能开销和时延,因此推荐开发者优先使用DragItemInfo中的PixelMap方式返回背板图。
privategetComponentSnapshot():void{
this.getUIContext().getComponentSnapshot().createFromBuilder(() =>{
this.pixelMapBuilder()
},
(error:Error, pixmap: image.PixelMap) =>{
if(error) {
hilog.error(0x0000,TAG,JSON.stringify(error));
return;
}
this.pixelMap= pixmap;
})
}
3. 在拖出对象的onDragStart()接口中,将回调的PixelMap作为拖拽中的背板图。
Image($r('app.media.mount'))
// ...
.onDragStart(() =>{
letdragItemInfo:DragItemInfo= {
pixelMap:this.pixelMap,
builder:() =>{
this.pixelMapBuilder()
},
extraInfo:"this is extraInfo"
};
returndragItemInfo;
})
AI识别拖拽内容
在拖拽过程中,可以自定义拖入响应,以识别拖拽内容并将其输出在释放区内。下面以通过AI识别拖拽图像中的文字为例,介绍实现原理。
实现原理
在拖入对象的onDrop()接口中,通过DragEvent的getData()接口获取拖拽数据后,调用系统文字识别能力textRecognition得到图像中的文字信息。
开发步骤
1. 在拖拽释放区域的allowDrop()接口中设置允许拖入的数据类型为uniformTypeDescriptor.UniformDataType.IMAGE。
// src/main/ets/pages/airecognition/AIRecognition.ets
Column() {
Text(this.textContent)
// ...
}
.allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE])
2. 在拖入对象的onDrop()接口中,调用DragEvent的getData()接口获取拖拽数据。
.onDrop(async(event?:DragEvent) => {
letdragData:UnifiedData= (eventasDragEvent).getData()asUnifiedData;
// ...
letrecord:Array = dragData.getRecords();
// ...
letimageSource = record[0]asunifiedDataChannel.Image;
// ...
})
3. 将拖拽数据转换成颜色数据格式为RGBA_8888的PixelMap类型的视觉信息。
constresourceReg =newRegExp('resource');
if(resourceReg.test(imageSource.uri)) {
constnumberReg =newRegExp('[0-9]+');
letidArray = imageSource.uri.match(numberReg);
if(idArray !==null) {
letid = idArray[0];
letdrawableDescriptor =this.context.resourceManager.getDrawableDescriptor(Number(id),0,1);
letpixelMapInit = drawableDescriptor.getPixelMap()asimage.PixelMap;
letimageHeight = pixelMapInit.getImageInfoSync().size.height;
letimageWidth = pixelMapInit.getImageInfoSync().size.width;
constreadBuffer:ArrayBuffer=newArrayBuffer(imageHeight * imageWidth *4);
pixelMapInit.readPixelsToBufferSync(readBuffer);
letopts: image.InitializationOptions= {
editable:true,
size: {height: imageHeight,width: imageWidth },
srcPixelFormat: pixelMapInit.getImageInfoSync().pixelFormat,
pixelFormat:3,
alphaType: pixelMapInit.getImageInfoSync().alphaType,
scaleMode:0
};
letpixelMap: image.PixelMap= image.createPixelMapSync(readBuffer, opts);
// ...
}
}
4. 调用系统文字识别能力textRecognition获取拖拽数据中的文字信息。
letvisionInfo: textRecognition.VisionInfo= {
pixelMap: pixelMap
};
letdata =awaittextRecognition.recognizeText(visionInfo);
letrecognitionString = data.value;
this.textContent= recognitionString;
分屏拖拽
将拖拽框架与系统的分屏能力结合,可以将数据从一个分屏页面拖拽到另一个分屏页面,实现跨应用拖拽或同应用跨页面拖拽。
使用说明
需要开启软件的分屏权限,并根据需求自定义拖拽响应。
跨设备拖拽
将拖拽框架与系统的键鼠穿越能力结合,可以接入跨设备拖拽,实现在平板或2in1类型的任意两台设备之间拖拽数据。
使用说明
需要满足跨设备拖拽开发指导中的使用限制条件,并根据需求自定义拖拽响应。
拖入小艺和中转站
将数据拖入系统的中转站,可以实现跨应用数据拖拽和跨设备数据流转;将数据拖入小艺,可以利用系统的AI能力处理拖拽数据。
使用限制
应用本身预置的资源文件(即应用在安装前的HAP包中已经存在的资源文件)不支持拖入小艺和中转站。
常见问题-在模拟器中无法实现AI识别拖拽内容
问题现象
将图像拖拽至释放区,无法识别图像中的文字并输出在释放区内。
解决措施
模拟器不支持textRecognition接口的调用,建议使用真机进行调试,详细请参见模拟器与真机的差异。
-
AI
+关注
关注
91文章
41980浏览量
303077 -
应用程序
+关注
关注
38文章
3347浏览量
60506 -
HarmonyOS
+关注
关注
80文章
2157浏览量
36409
原文标题:HarmonyOS应用统一拖拽解决方案
文章出处:【微信号:HarmonyOS_Dev,微信公众号:HarmonyOS开发者】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
基于Linux的LDAP统一认证解决方案
中央空调一拖一和一拖多的意思是什么 有什么区别
3CX统一通信解决方案,解决企业通信问题
HarmonyOS开发文档(一)
意法半导体与中国一拖设立联合实验室,专注于农业电子解决方案
日立统一计算平台选择SAP HANA:融合横向扩展解决方案
Hitachi统一计算平台(UCP)解决方案与Brocade网络
HarmonyOS应用统一拖拽解决方案



评论