概述
拖拽操作是一種直觀且高效的數(shù)據(jù)傳輸方式,它允許用戶通過標(biāo)準(zhǔn)手勢(shì)(包括用手指、鼠標(biāo)或觸控筆按住并移動(dòng))在應(yīng)用程序之間及內(nèi)部進(jìn)行數(shù)據(jù)傳輸。
拖拽功能不僅操作便捷,還能與多種系統(tǒng)能力深度融合,拓展出更為廣泛的應(yīng)用場(chǎng)景。例如,跨設(shè)備拖拽讓用戶能在不同設(shè)備間無縫傳輸數(shù)據(jù),跨窗口拖拽提升了多任務(wù)處理的靈活性。此外,基于拖拽操作還可以開發(fā)出更多創(chuàng)新性的應(yīng)用場(chǎng)景,如AI智能識(shí)別、水印添加等,這些創(chuàng)新性的功能接入統(tǒng)稱為“統(tǒng)一拖拽”。
本文將介紹幾種典型拖拽場(chǎng)景及其具體實(shí)現(xiàn)方案,幫助開發(fā)者更好地理解和應(yīng)用拖拽技術(shù)。
通過設(shè)置組件的拖拽響應(yīng),可以自定義拖出數(shù)據(jù)、拖入數(shù)據(jù)和拖拽背板圖,實(shí)現(xiàn)如下場(chǎng)景:
拖拽圖像增加水?。簽橥献У膱D像添加水印,水印內(nèi)容為圖像的拖拽時(shí)間。開發(fā)者可以在應(yīng)用時(shí)根據(jù)需求自定義水印內(nèi)容,例如標(biāo)記拖拽圖片的來源信息,為圖像管理與溯源提供便利。
自定義拖拽背板圖:將拖拽中的背板圖設(shè)置為自定義數(shù)據(jù)內(nèi)容。開發(fā)者可根據(jù)個(gè)性化需求打造獨(dú)特的拖拽視覺效果。
AI識(shí)別拖拽內(nèi)容:通過在接收拖拽內(nèi)容時(shí)增加AI識(shí)別功能,使得只能顯示文字的組件可以接收?qǐng)D片拖拽并顯示圖片中的文字信息。開發(fā)者可以將此能力應(yīng)用于拖拽識(shí)圖搜索。
將拖拽框架與系統(tǒng)的分屏能力、鍵鼠穿越能力、小藝及中轉(zhuǎn)站結(jié)合,可以實(shí)現(xiàn)如下場(chǎng)景:
分屏拖拽:演示了分屏拖拽的功能,可以在分屏中打開兩個(gè)不同的應(yīng)用,實(shí)現(xiàn)跨應(yīng)用拖拽。
跨設(shè)備拖拽:演示了基于鍵鼠穿越能力的跨設(shè)備拖拽,可以在平板和2in1設(shè)備中使用此功能以直觀便捷地交換數(shù)據(jù)。
拖入小藝和中轉(zhuǎn)站:演示了小藝和中轉(zhuǎn)站與拖拽框架結(jié)合的能力,可以利用中轉(zhuǎn)站暫存拖拽內(nèi)容或進(jìn)行跨設(shè)備拖拽,也可以利用小藝的AI對(duì)話式分析能力處理拖拽內(nèi)容。
實(shí)現(xiàn)原理
拖拽流程可以分為三部分:發(fā)起拖拽、拖拽中和釋放拖拽。其中,拖出方通過draggable()和onDragStart()等接口處理拖出數(shù)據(jù),拖入方通過allowDrop()和onDrop()等接口處理拖入數(shù)據(jù),拖拽數(shù)據(jù)使用UDMF統(tǒng)一數(shù)據(jù)對(duì)象UnifiedData 進(jìn)行封裝。下面,將按照這三個(gè)部分依次介紹拖拽的基礎(chǔ)實(shí)現(xiàn)。
| 發(fā)起拖拽 | 拖拽中 | 釋放拖拽 |
![]() |
![]() |
![]() |
表1 拖拽流程展示
發(fā)起拖拽
默認(rèn)支持拖出能力的組件,如Search、Hyperlink等,在拖出時(shí)會(huì)使用組件的默認(rèn)拖出響應(yīng)。其中Search組件默認(rèn)拖拽內(nèi)容為選中的文字,Hyperlink組件默認(rèn)拖拽內(nèi)容為超鏈接地址。如果想自定義組件的拖拽內(nèi)容,需要在組件的onDragStart()接口中將自定義數(shù)據(jù)封裝成UnifiedData數(shù)據(jù)對(duì)象,通過DragEvent的setData()接口設(shè)置拖出數(shù)據(jù)。對(duì)于其他非默認(rèn)組件或自定義組件,如果想實(shí)現(xiàn)其拖出功能,需要將組件的draggable()屬性設(shè)置為true,并自定義組件的拖拽內(nèi)容。以Text組件為例,示例代碼如下:
Text('自定義拖出響應(yīng),拖拽video')
.draggable(true)
.onDragStart((event) =>{
// 處理拖出數(shù)據(jù)
letvideo: unifiedDataChannel.Video=newunifiedDataChannel.Video();
video.videoUri='/resources/rawfile/01.mp4';
letdata: unifiedDataChannel.UnifiedData=newunifiedDataChannel.UnifiedData(video);
(eventasDragEvent).setData(data);
})
可以在onDragStart()中自由地處理拖拽信息,例如為圖片添加水印,詳情見拖拽圖像增加水印。
拖拽中
通過標(biāo)準(zhǔn)手勢(shì)發(fā)起拖拽后,系統(tǒng)會(huì)默認(rèn)將組件本身的截圖作為拖拽移動(dòng)中的背板圖。如果想自定義拖拽背板圖,需要在組件的onDragStart()接口中通過回調(diào)的CustomBuilder或DragItemInfo進(jìn)行設(shè)置。以Text組件為例,示例代碼如下:
Text('自定義拖拽背板圖')
.draggable(true)
.onDragStart(() =>{
// 返回自定義背板圖
letdragItemInfo:DragItemInfo= {
pixelMap:this.pixelMap,
builder:() =>{this.pixelMapBuilder() },
extraInfo:"this is extraInfo",
};
returndragItemInfo;
})
可以將拖拽背板圖設(shè)置為自定義的圖片或者文字,詳情見自定義拖拽背板圖。
釋放拖拽
默認(rèn)支持拖入能力的組件,如Search等,將目標(biāo)拖入組件區(qū)域內(nèi)會(huì)使用默認(rèn)拖入響應(yīng)。如果想自定義組件的拖入響應(yīng),需要將組件的allowDrop()屬性設(shè)置為允許拖入的數(shù)據(jù)類型,并在其onDrop()接口中通過DragEvent的getData()接口獲取拖入數(shù)據(jù)后,對(duì)數(shù)據(jù)內(nèi)容進(jìn)行相應(yīng)處理。
Text(this.targetText)
.allowDrop([uniformTypeDescriptor.UniformDataType.PLAIN_TEXT])
.onDrop((event: DragEvent) =>{
// 處理拖入數(shù)據(jù)
letrecords:Array = event.getData().getRecords();
letplainText: unifiedDataChannel.PlainText= records[0]asunifiedDataChannel.PlainText;
this.targetText= plainText.textContent;
})
可以在onDrop()中處理接收到的數(shù)據(jù),例如將圖片識(shí)別為文字以顯示在只支持文字的組件上,詳情見AI識(shí)別拖拽內(nèi)容。
拖拽圖像增加水印
在拖拽過程中,可以自定義拖出響應(yīng),為拖拽圖像增加水印,以標(biāo)識(shí)圖像的相關(guān)信息。下面以在圖像中增加拖拽時(shí)間水印為例,介紹實(shí)現(xiàn)原理。
實(shí)現(xiàn)原理
在拖出對(duì)象的onDragStart()接口中獲取圖像信息,調(diào)用系統(tǒng)繪制能力drawing在圖像上繪制水印,通過DragEvent的setData()接口將水印圖像設(shè)置為拖拽數(shù)據(jù)。
開發(fā)步驟
1. 將Image的draggable()屬性設(shè)置為true。
// src/main/ets/pages/watermark/Watermark.ets
Image($rawfile('river.png'))
// ...
.draggable(true)
2. 在拖出對(duì)象的onDragStart()接口中,獲取圖像信息并將其轉(zhuǎn)換成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畫布上,并獲取拖拽時(shí)間作為水印繪制到畫布上的指定位置,得到添加水印的圖像。
// 獲取拖拽時(shí)間 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. 將圖像打包保存在文件中,調(diào)用DragEvent的setData()接口將水印圖像設(shè)置為拖拽數(shù)據(jù)。
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);
自定義拖拽背板圖
在拖拽過程中,可以自定義拖拽背板圖,展示拖拽數(shù)據(jù)的相關(guān)信息。
實(shí)現(xiàn)原理
在拖出對(duì)象的onDragStart()接口中,回調(diào)自定義的PixelMap作為拖拽中的背板圖。
開發(fā)步驟
1. 創(chuàng)建自定義組件。
// 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. 將自定義組件轉(zhuǎn)換成PixelMap,作為拖拽過程中顯示的圖片。
說明:由于CustomBuilder需要離線渲染之后才能使用,存在一定的性能開銷和時(shí)延,因此推薦開發(fā)者優(yōu)先使用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. 在拖出對(duì)象的onDragStart()接口中,將回調(diào)的PixelMap作為拖拽中的背板圖。
Image($r('app.media.mount'))
// ...
.onDragStart(() =>{
letdragItemInfo:DragItemInfo= {
pixelMap:this.pixelMap,
builder:() =>{
this.pixelMapBuilder()
},
extraInfo:"this is extraInfo"
};
returndragItemInfo;
})
AI識(shí)別拖拽內(nèi)容
在拖拽過程中,可以自定義拖入響應(yīng),以識(shí)別拖拽內(nèi)容并將其輸出在釋放區(qū)內(nèi)。下面以通過AI識(shí)別拖拽圖像中的文字為例,介紹實(shí)現(xiàn)原理。
實(shí)現(xiàn)原理
在拖入對(duì)象的onDrop()接口中,通過DragEvent的getData()接口獲取拖拽數(shù)據(jù)后,調(diào)用系統(tǒng)文字識(shí)別能力textRecognition得到圖像中的文字信息。
開發(fā)步驟
1. 在拖拽釋放區(qū)域的allowDrop()接口中設(shè)置允許拖入的數(shù)據(jù)類型為uniformTypeDescriptor.UniformDataType.IMAGE。
// src/main/ets/pages/airecognition/AIRecognition.ets
Column() {
Text(this.textContent)
// ...
}
.allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE])
2. 在拖入對(duì)象的onDrop()接口中,調(diào)用DragEvent的getData()接口獲取拖拽數(shù)據(jù)。
.onDrop(async(event?:DragEvent) => {
letdragData:UnifiedData= (eventasDragEvent).getData()asUnifiedData;
// ...
letrecord:Array = dragData.getRecords();
// ...
letimageSource = record[0]asunifiedDataChannel.Image;
// ...
})
3. 將拖拽數(shù)據(jù)轉(zhuǎn)換成顏色數(shù)據(jù)格式為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. 調(diào)用系統(tǒng)文字識(shí)別能力textRecognition獲取拖拽數(shù)據(jù)中的文字信息。
letvisionInfo: textRecognition.VisionInfo= {
pixelMap: pixelMap
};
letdata =awaittextRecognition.recognizeText(visionInfo);
letrecognitionString = data.value;
this.textContent= recognitionString;
分屏拖拽
將拖拽框架與系統(tǒng)的分屏能力結(jié)合,可以將數(shù)據(jù)從一個(gè)分屏頁面拖拽到另一個(gè)分屏頁面,實(shí)現(xiàn)跨應(yīng)用拖拽或同應(yīng)用跨頁面拖拽。
使用說明
需要開啟軟件的分屏權(quán)限,并根據(jù)需求自定義拖拽響應(yīng)。
跨設(shè)備拖拽
將拖拽框架與系統(tǒng)的鍵鼠穿越能力結(jié)合,可以接入跨設(shè)備拖拽,實(shí)現(xiàn)在平板或2in1類型的任意兩臺(tái)設(shè)備之間拖拽數(shù)據(jù)。
使用說明
需要滿足跨設(shè)備拖拽開發(fā)指導(dǎo)中的使用限制條件,并根據(jù)需求自定義拖拽響應(yīng)。
拖入小藝和中轉(zhuǎn)站
將數(shù)據(jù)拖入系統(tǒng)的中轉(zhuǎn)站,可以實(shí)現(xiàn)跨應(yīng)用數(shù)據(jù)拖拽和跨設(shè)備數(shù)據(jù)流轉(zhuǎn);將數(shù)據(jù)拖入小藝,可以利用系統(tǒng)的AI能力處理拖拽數(shù)據(jù)。
使用限制
應(yīng)用本身預(yù)置的資源文件(即應(yīng)用在安裝前的HAP包中已經(jīng)存在的資源文件)不支持拖入小藝和中轉(zhuǎn)站。
常見問題-在模擬器中無法實(shí)現(xiàn)AI識(shí)別拖拽內(nèi)容
問題現(xiàn)象
將圖像拖拽至釋放區(qū),無法識(shí)別圖像中的文字并輸出在釋放區(qū)內(nèi)。
解決措施
模擬器不支持textRecognition接口的調(diào)用,建議使用真機(jī)進(jìn)行調(diào)試,詳細(xì)請(qǐng)參見模擬器與真機(jī)的差異。
-
AI
+關(guān)注
關(guān)注
91文章
41315瀏覽量
302701 -
應(yīng)用程序
+關(guān)注
關(guān)注
38文章
3346瀏覽量
60428 -
HarmonyOS
+關(guān)注
關(guān)注
80文章
2157瀏覽量
36320
原文標(biāo)題:HarmonyOS應(yīng)用統(tǒng)一拖拽解決方案
文章出處:【微信號(hào):HarmonyOS_Dev,微信公眾號(hào):HarmonyOS開發(fā)者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
基于Linux的LDAP統(tǒng)一認(rèn)證解決方案
ADMS統(tǒng)一標(biāo)準(zhǔn)化集成管理解決方案
中央空調(diào)一拖一和一拖多的意思是什么 有什么區(qū)別
3CX統(tǒng)一通信解決方案,解決企業(yè)通信問題
HarmonyOS開發(fā)文檔(一)
意法半導(dǎo)體與中國一拖設(shè)立聯(lián)合實(shí)驗(yàn)室,專注于農(nóng)業(yè)電子解決方案
HarmonyOS測(cè)試技術(shù)與實(shí)戰(zhàn)-分布式應(yīng)用測(cè)試解決方案
日立統(tǒng)一計(jì)算平臺(tái)選擇SAP HANA:融合橫向擴(kuò)展解決方案
Hitachi統(tǒng)一計(jì)算平臺(tái)(UCP)解決方案與Brocade網(wǎng)絡(luò)
Type-C一拖二/一拖三快充數(shù)據(jù)線方案介紹
HarmonyOS應(yīng)用統(tǒng)一拖拽解決方案



評(píng)論