一、概述
拖拽操作是一种直观且高效的数据传输方式,它答应用户通过标准手势(包括用手指、鼠标或触控笔按住并移动)在应用步伐之间及内部进行数据传输。
拖拽功能不仅操作便捷,还能与多种系统本领深度融合,拓展出更为广泛的应用场景。例如,跨设备拖拽让用户能在差别设备间无缝传输数据,跨窗口拖拽提拔了多任务处理的机动性。此外,基于拖拽操作还可以开发出更多创新性的应用场景,如AI智能识别、水印添加等,这些创新性的功能接入统称为“统一拖拽”。
下面将介绍几种典型拖拽场景及其具体实现方案,帮助开发者更好地明确和应用拖拽技术。
通过设置组件的拖拽相应,可以自定义拖出数据、拖入数据和拖拽背板图,实现如下场景:
- 拖拽图像增加水印:为拖拽的图像添加水印,水印内容为图像的拖拽时间。开发者可以在应用时根据需求自定义水印内容,
如标志拖拽图片的来源信息,为图像管理与溯源提供便利。
- 自定义拖拽背板图:将拖拽中的背板图设置为自定义数据内容。开发者可根据个性化需求打造独特的拖拽视觉结果。
- AI识别拖拽内容:通过在吸收拖拽内容时增加AI识别功能,使得只能表现文字的组件可以吸收图片拖拽并表现图片中的文字信息。开发者可以将此本领应用于拖拽识图搜索。
将拖拽框架与系统的分屏本领、键鼠穿越本领、小艺及中转站联合,可以实现如下场景:
- 分屏拖拽:演示了分屏拖拽的功能,可以在分屏中打开两个差别的应用,实现跨应用拖拽。
- 跨设备拖拽:演示了基于键鼠穿越本领的跨设备拖拽,可以在平板和PC/2in1设备中使用此功能以直观便捷地交换数据。
- 拖入小艺和中转站:演示了小艺和中转站与拖拽框架联合的本领,可以利用中转站暂存拖拽内容或进行跨设备拖拽,也可以利用小艺的AI对话式分析本领处理拖拽内容。
二、实现原理
拖拽流程可以分为三部分:发起拖拽、拖拽中和释放拖拽。此中,拖出方通过 draggable() 和 onDragStart() 等接口处理拖出数据,拖入方通过allowDrop()和onDrop()等接口处理拖入数据,拖拽数据使用UDMF统一数据对象UnifiedData 进行封装。下面,将按照这三个部分依次介绍拖拽的基础实现。
拖拽流程展示图如下:
1. 发起拖拽
2. 拖拽中
3. 释放拖拽
拖拽图片示例代码
三、拖拽图像增加水印
在拖拽过程中,可以自定义拖出相应,为拖拽图像增加水印,以标识图像的相关信息。下面以在图像中增加拖拽时间水印为例,介绍实现原理。
3.1 实现原理
在拖出对象的onDragStart()接口中获取图像信息,调用系统绘制本领drawing在图像上绘制水印,通过DragEvent的setData()接口将水印图像设置为拖拽数据。
3.2 开发步调
- 将Image的draggable()属性设置为true。
- 在拖出对象的onDragStart()接口中,获取图像信息并将其转换成PixelMap。
- 将图片绘制到Canvas画布上,并获取拖拽时间作为水印绘制到画布上的指定位置,得到添加水印的图像。
- 将图像打包生存在文件中,调用DragEvent的setData()接口将水印图像设置为拖拽数据。
3.3 结果图
拖拽中
释放拖拽
拖拽图像增加水印示例代码
- import { display, promptAction, SubHeader, TextModifier } from '@kit.ArkUI';
- import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData'
- import { image } from '@kit.ImageKit'
- import { drawing } from '@kit.ArkGraphics2D'
- import { fileIo as fs, fileUri } from '@kit.CoreFileKit'
- import { resourceManager } from '@kit.LocalizationKit';
- import { hilog } from '@kit.PerformanceAnalysisKit';
- import { BusinessError, systemDateTime } from '@kit.BasicServicesKit';
- const TAG = '[统一拖拽]';
- @Component
- struct TestDragImageWatermarkDrag {
- @State targetImage: string = '';
- @State primaryModifier: TextModifier =
- new TextModifier().fontColor(Color.Gray).fontSize(18).fontWeight(FontWeight.Medium);
- context: Context = getContext(this);
- time: string = '0';
- getTimeWatermark(str: number): string {
- let time: string = '';
- let date = new Date(str);
- try {
- let year = date.getFullYear();
- let month = (date.getMonth() + 1) < 10 ? '0' + (date.getMonth() + 1) : (date.getMonth() + 1);
- let day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate();
- let hour = date.getHours() < 10 ? '0' + date.getHours() : date.getHours();
- let min = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes();
- let second = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds();
- time = year + '-' + month + '-' + day + ' ' + hour + ':' + min + ':' + second;
- hilog.info(0x0000, TAG, `${date}transform===>${time}`);
- } catch (error) {
- hilog.error(0x0000, TAG,
- `Failed to get currentTime, code = ${(error as BusinessError).code}, message = ${(error as BusinessError).message}`);
- }
- return time;
- }
- 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 (error) {
- hilog.error(0x0000, TAG,
- `getData failed, code = ${(error as BusinessError).code}, message = ${(error as BusinessError).message}`);
- return false;
- }
- }
- getDataFromUdmf(event: DragEvent, callback: (data: DragEvent) => void) {
- if (this.getDataFromUdmfRetry(event, callback)) {
- return;
- }
- setTimeout(() => {
- this.getDataFromUdmfRetry(event, callback);
- }, 1500);
- }
- addWaterMark(watermark: string, pixelMap: image.PixelMap) {
- if (canIUse('SystemCapability.Graphics.Drawing')) {
- watermark = this.context.resourceManager.getStringSync($r('app.string.drag_time')) + watermark;
- let imageWidth = pixelMap.getImageInfoSync().size.width;
- let imageHeight = pixelMap.getImageInfoSync().size.height;
- let imageScale = imageWidth / display.getDefaultDisplaySync().width;
- const canvas = new drawing.Canvas(pixelMap);
- const pen = new drawing.Pen();
- const brush = new drawing.Brush();
- pen.setColor({
- alpha: 102,
- red: 255,
- green: 255,
- blue: 255
- })
- brush.setColor({
- alpha: 102,
- red: 255,
- green: 255,
- blue: 255
- })
- const font = new drawing.Font();
- font.setSize(48 * imageScale);
- let textWidth = font.measureText(watermark, drawing.TextEncoding.TEXT_ENCODING_UTF8);
- const textBlob = 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');
- }
- return pixelMap;
- }
- build() {
- Column({ space: 10 }) {
- SubHeader({
- primaryTitle: '拖拽图像增加水印',
- primaryTitleModifier: this.primaryModifier
- })
- Image($rawfile('river.png'))
- .width('342vp')
- .fitOriginalSize(true)
- .borderRadius(8)
- .draggable(true)
- .onDragStart((event: DragEvent) => {
- const resourceMgr: resourceManager.ResourceManager = this.context.resourceManager;
- let rawFileDescriptor = resourceMgr.getRawFdSync('river.png');
- const imageSourceApi: image.ImageSource = image.createImageSource(rawFileDescriptor);
- let pixelMap = imageSourceApi.createPixelMapSync();
- this.time = this.getTimeWatermark(systemDateTime.getTime(false));
- let markPixelMap = this.addWaterMark(this.time, pixelMap);
- let packOpts: image.PackingOption = { format: 'image/png', quality: 20 };
- let file =
- fs.openSync(`${this.context.filesDir}/watermark.png`, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
- const imagePackerApi: image.ImagePacker = image.createImagePacker();
- imagePackerApi.packToFile(markPixelMap, file.fd, packOpts);
- let img: unifiedDataChannel.Image = new unifiedDataChannel.Image();
- img.imageUri = fileUri.getUriFromPath(`${this.context.filesDir}/watermark.png`);
- let data: unifiedDataChannel.UnifiedData = new unifiedDataChannel.UnifiedData(img);
- (event as DragEvent).setData(data);
- fs.closeSync(file.fd);
- })
- .onDragEnd((event) => {
- if (event.getResult() === DragResult.DRAG_SUCCESSFUL) {
- promptAction.showToast({
- duration: 100,
- bottom: '80vp',
- message: $r('app.string.drag_successfully')
- });
- } else if (event.getResult() === DragResult.DRAG_FAILED) {
- promptAction.showToast({ duration: 100, bottom: '80vp', message: $r('app.string.drag_failed') });
- }
- })
- SubHeader({
- primaryTitle: $r('app.string.area_can_drag'),
- primaryTitleModifier: this.primaryModifier
- })
- Column() {
- Image(this.targetImage)
- .width('342vp')
- .fitOriginalSize(true)
- .borderRadius(8)
- .draggable(true)
- }
- .alignItems(HorizontalAlign.Center)
- .allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE])
- .onDrop((dragEvent?: DragEvent) => {
- this.getDataFromUdmf((dragEvent as DragEvent), (event: DragEvent) => {
- let records: Array<unifiedDataChannel.UnifiedRecord> = event.getData().getRecords();
- this.targetImage = (records[0] as unifiedDataChannel.Image).imageUri;
- event.useCustomDropAnimation = false;
- event.setResult(DragResult.DRAG_SUCCESSFUL);
- })
- })
- .backgroundColor(Color.Gray)
- .width('342vp')
- .constraintSize({ maxWidth: '100%' })
- .height('193vp')
- .borderRadius(8)
- .margin({
- left: '16vp',
- right: '16vp'
- })
- }
- .height('100%')
- }
- }
- @Entry
- @Component
- struct TestDrag {
- @State message: string = '统一拖拽';
- build() {
- Scroll() {
- Column({ space: 10 }) {
- Text(this.message)
- .id('TestDragHelloWorld')
- .fontSize(20)
- .fontWeight(FontWeight.Medium)
- // 拖拽图片
- // TestDragImage()
- //拖拽图片添加水印
- TestDragImageWatermarkDrag()
- }
- }
- .height('100%')
- .width('100%')
- }
- }
复制代码 四、自定义拖拽背板图
在拖拽过程中,可以自定义拖拽背板图,展示拖拽数据的相关信息。
4.1 实现原理
在拖出对象的onDragStart()接口中,回调自定义的PixelMap作为拖拽中的背板图。
4.2 开发步调
- 创建自定义组件。
- 将自定义组件转换成PixelMap,作为拖拽过程中表现的图片。
说明
由于CustomBuilder需要离线渲染之后才能使用,存在肯定的性能开销和时延,因此保举开发者优先使用DragItemInfo中的PixelMap方式返回背板图。
- 在拖出对象的onDragStart()接口中,将回调的PixelMap作为拖拽中的背板图。
4.3 结果图
拖拽中
释放拖拽,拖拽成功
自定义拖拽背板图示例代码
- import { display, promptAction, SubHeader, TextModifier } from '@kit.ArkUI';
- import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData'
- import { image } from '@kit.ImageKit'
- import { drawing } from '@kit.ArkGraphics2D'
- import { fileIo as fs, fileUri } from '@kit.CoreFileKit'
- import { resourceManager } from '@kit.LocalizationKit';
- import { hilog } from '@kit.PerformanceAnalysisKit';
- import { BusinessError, systemDateTime } from '@kit.BasicServicesKit';
- const TAG = '[统一拖拽]';
- @Component
- struct TestBackgroundDrag {
- @State targetImage: string = '';
- @State primaryModifier: TextModifier =
- new TextModifier().fontColor(Color.Gray).fontSize(18).fontWeight(FontWeight.Medium);
- @State pixelMap: image.PixelMap | undefined = undefined;
- @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)
- }
- private getComponentSnapshot(): 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;
- })
- }
- private PreDragChange(preDragStatus: PreDragStatus): void {
- if (preDragStatus == PreDragStatus.ACTION_DETECTING_STATUS) {
- this.getComponentSnapshot();
- }
- }
- build() {
- Column({ space: 10 }) {
- SubHeader({
- primaryTitle: '自定义拖拽背板图',
- primaryTitleModifier: this.primaryModifier
- })
- Image($r('app.media.mount'))
- .width('342vp')
- .fitOriginalSize(true)
- .borderRadius(8)
- .draggable(true)
- .onPreDrag((status: PreDragStatus) => {
- this.PreDragChange(status);
- })
- .onDragStart(() => {
- let dragItemInfo: DragItemInfo = {
- pixelMap: this.pixelMap,
- builder: () => {
- this.pixelMapBuilder()
- },
- extraInfo: "this is extraInfo"
- };
- return dragItemInfo;
- })
- .onDragEnd((event) => {
- if (event.getResult() === DragResult.DRAG_SUCCESSFUL) {
- promptAction.showToast({
- duration: 100,
- bottom: '80vp',
- message: $r('app.string.drag_successfully')
- });
- } else if (event.getResult() === DragResult.DRAG_FAILED) {
- promptAction.showToast({ duration: 100, bottom: '80vp', message: $r('app.string.drag_failed') });
- }
- })
- SubHeader({
- primaryTitle: $r('app.string.area_can_drag'),
- primaryTitleModifier: this.primaryModifier
- })
- Column() {
- Image(this.targetImage)
- .height('193vp')
- .fitOriginalSize(true)
- .constraintSize({ maxWidth: '100%' })
- .borderRadius(8)
- }
- .backgroundColor(Color.Grey)
- .height('193vp')
- .width('342vp')
- .constraintSize({ maxWidth: '100%' })
- .borderRadius(8)
- .allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE])
- .onDrop((event?: DragEvent) => {
- let dragData: UnifiedData = (event as DragEvent).getData() as UnifiedData;
- if (dragData !== undefined) {
- let arr: Array<unifiedDataChannel.UnifiedRecord> = dragData.getRecords();
- if (arr.length > 0) {
- this.targetImage = (arr[0] as unifiedDataChannel.Image).imageUri;
- } else {
- hilog.info(0x0000, TAG, 'dragData arr is null');
- }
- } else {
- hilog.info(0x0000, TAG, 'dragData is undefined');
- }
- event?.setResult(DragResult.DRAG_SUCCESSFUL);
- })
- .margin({
- left: '16vp',
- right: '16vp'
- })
- }
- .height('100%')
- }
- }
- @Entry
- @Component
- struct TestDrag {
- @State message: string = '统一拖拽';
- build() {
- Scroll() {
- Column({ space: 10 }) {
- Text(this.message)
- .id('TestDragHelloWorld')
- .fontSize(20)
- .fontWeight(FontWeight.Medium)
- //拖拽图片
- // TestDragImage()
- // 拖拽图片添加水印
- // TestDragImageWatermarkDrag()
- // 自定义拖拽背板图
- TestBackgroundDrag()
- }
- }
- .height('100%')
- .width('100%')
- }
- }
复制代码 五、AI识别拖拽内容
在拖拽过程中,可以自定义拖入相应,以识别拖拽内容并将其输出在释放区内。下面以通过AI识别拖拽图像中的文字为例,介绍实现原理。
5.1 实现原理
在拖入对象的onDrop()接口中,通过DragEvent的getData()接口获取拖拽数据后,调用系统文字识别本领textRecognition得到图像中的文字信息。
5.2 开发步调
- 在拖拽释放地区的allowDrop()接口中设置答应拖入的数据类型为uniformTypeDescriptor.UniformDataType.IMAGE。
- 在拖入对象的onDrop()接口中,调用DragEvent的getData()接口获取拖拽数据。
- 将拖拽数据转换成颜色数据格式为RGBA_8888的PixelMap类型的视觉信息。
- 调用系统文字识别本领textRecognition获取拖拽数据中的文字信息。
5.3 结果图
拖拽中
释放拖拽,识别成功
AI识别拖拽内容示例代码
- import { display, promptAction, SubHeader, TextModifier } from '@kit.ArkUI';
- import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData'
- import { image } from '@kit.ImageKit'
- import { drawing } from '@kit.ArkGraphics2D'
- import { fileIo as fs, fileUri } from '@kit.CoreFileKit'
- import { resourceManager } from '@kit.LocalizationKit';
- import { hilog } from '@kit.PerformanceAnalysisKit';
- import { BusinessError, systemDateTime } from '@kit.BasicServicesKit';
- import { textRecognition } from '@kit.CoreVisionKit';
- const TAG = '[统一拖拽]';
- @Component
- struct TestAIRecognitionDrag {
- @State textContent: string = '';
- @State primaryModifier: TextModifier =
- new TextModifier().fontColor(Color.Gray).fontSize(18).fontWeight(FontWeight.Medium);
- context: Context = getContext(this);
- build() {
- Column({ space: 10 }) {
- SubHeader({
- primaryTitle: 'AI识别拖拽内容',
- primaryTitleModifier: this.primaryModifier
- })
- Image($r('app.media.architecture'))
- .width('355vp')
- .fitOriginalSize(true)
- .borderRadius(8)
- .draggable(true)
- .onDragEnd((event) => {
- if (event.getResult() === DragResult.DRAG_SUCCESSFUL) {
- promptAction.showToast({
- duration: 100,
- bottom: '80vp',
- message: $r('app.string.drag_successfully')
- });
- } else if (event.getResult() === DragResult.DRAG_FAILED) {
- promptAction.showToast({ duration: 100, bottom: '80vp', message: $r('app.string.drag_failed') });
- }
- })
- SubHeader({
- primaryTitle: $r('app.string.area_can_drag'),
- primaryTitleModifier: this.primaryModifier
- })
- Column() {
- Text(this.textContent)
- .width('100%')
- .height('175vp')
- .lineHeight(22)
- .borderRadius(8)
- .align(Alignment.TopStart)
- }
- .allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE])
- .onDrop(async (event?: DragEvent) => {
- let dragData: UnifiedData = (event as DragEvent).getData() as UnifiedData;
- if (dragData === undefined) {
- hilog.info(0x0000, TAG, 'ondrop undefined data');
- return;
- }
- let record: Array<unifiedDataChannel.UnifiedRecord> = dragData.getRecords();
- if (record.length <= 0) {
- hilog.info(0x0000, TAG, 'dragData arr is null');
- return;
- }
- let imageSource = record[0] as unifiedDataChannel.Image;
- const resourceReg = new RegExp('resource');
- if (resourceReg.test(imageSource.uri)) {
- const numberReg = new RegExp('[0-9]+');
- let idArray = imageSource.uri.match(numberReg);
- if (idArray !== null) {
- let id = idArray[0];
- let drawableDescriptor = this.context.resourceManager.getDrawableDescriptor(Number(id), 0, 1);
- let pixelMapInit = drawableDescriptor.getPixelMap() as image.PixelMap;
- let imageHeight = pixelMapInit.getImageInfoSync().size.height;
- let imageWidth = pixelMapInit.getImageInfoSync().size.width;
- const readBuffer: ArrayBuffer = new ArrayBuffer(imageHeight * imageWidth * 4);
- pixelMapInit.readPixelsToBufferSync(readBuffer);
- let opts: image.InitializationOptions = {
- editable: true,
- size: { height: imageHeight, width: imageWidth },
- srcPixelFormat: pixelMapInit.getImageInfoSync().pixelFormat,
- pixelFormat: 3,
- alphaType: pixelMapInit.getImageInfoSync().alphaType,
- scaleMode: 0
- };
- let pixelMap: image.PixelMap = image.createPixelMapSync(readBuffer, opts);
- let visionInfo: textRecognition.VisionInfo = {
- pixelMap: pixelMap
- };
- let data = await textRecognition.recognizeText(visionInfo);
- let recognitionString = data.value;
- this.textContent = recognitionString;
- }
- }
- })
- .backgroundColor(Color.Grey)
- .borderRadius(8)
- .padding({
- left: '16vp',
- right: '16vp',
- top: '8vp',
- bottom: '8vp'
- })
- .margin({
- left: '16vp',
- right: '16vp'
- })
- }
- .height('100%')
- }
- }
- @Entry
- @Component
- struct TestDrag {
- @State message: string = '统一拖拽';
- build() {
- Scroll() {
- Column({ space: 10 }) {
- Text(this.message)
- .id('TestDragHelloWorld')
- .fontSize(20)
- .fontWeight(FontWeight.Medium)
- //拖拽图片
- // TestDragImage()
- // 拖拽图片添加水印
- // TestDragImageWatermarkDrag()
- // 自定义拖拽背板图
- // TestBackgroundDrag()
- //AI识别拖拽内容
- TestAIRecognitionDrag()
- }
- }
- .height('100%')
- .width('100%')
- }
- }
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |