概述
拖拽操作是一種直觀且高效的數(shù)據(jù)傳輸方式,它允許用戶通過(guò)標(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è)備間無(wú)縫傳輸數(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ù)。
通過(guò)設(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)記拖拽圖片的來(lái)源信息,為圖像管理與溯源提供便利。
自定義拖拽背板圖:將拖拽中的背板圖設(shè)置為自定義數(shù)據(jù)內(nèi)容。開發(fā)者可根據(jù)個(gè)性化需求打造獨(dú)特的拖拽視覺(jué)效果。
AI識(shí)別拖拽內(nèi)容:通過(guò)在接收拖拽內(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ā)起拖拽、拖拽中和釋放拖拽。其中,拖出方通過(guò)draggable()和onDragStart()等接口處理拖出數(shù)據(jù),拖入方通過(guò)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ì)象,通過(guò)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()中自由地處理拖拽信息,例如為圖片添加水印,詳情見拖拽圖像增加水印。
拖拽中
通過(guò)標(biāo)準(zhǔn)手勢(shì)發(fā)起拖拽后,系統(tǒng)會(huì)默認(rèn)將組件本身的截圖作為拖拽移動(dòng)中的背板圖。如果想自定義拖拽背板圖,需要在組件的onDragStart()接口中通過(guò)回調(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()接口中通過(guò)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)容。
拖拽圖像增加水印
在拖拽過(guò)程中,可以自定義拖出響應(yīng),為拖拽圖像增加水印,以標(biāo)識(shí)圖像的相關(guān)信息。下面以在圖像中增加拖拽時(shí)間水印為例,介紹實(shí)現(xiàn)原理。
實(shí)現(xiàn)原理
在拖出對(duì)象的onDragStart()接口中獲取圖像信息,調(diào)用系統(tǒng)繪制能力drawing在圖像上繪制水印,通過(guò)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);
自定義拖拽背板圖
在拖拽過(guò)程中,可以自定義拖拽背板圖,展示拖拽數(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,作為拖拽過(guò)程中顯示的圖片。
說(shuō)明:由于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)容
在拖拽過(guò)程中,可以自定義拖入響應(yīng),以識(shí)別拖拽內(nèi)容并將其輸出在釋放區(qū)內(nèi)。下面以通過(guò)AI識(shí)別拖拽圖像中的文字為例,介紹實(shí)現(xiàn)原理。
實(shí)現(xiàn)原理
在拖入對(duì)象的onDrop()接口中,通過(guò)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類型的視覺(jué)信息。
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è)分屏頁(yè)面拖拽到另一個(gè)分屏頁(yè)面,實(shí)現(xiàn)跨應(yīng)用拖拽或同應(yīng)用跨頁(yè)面拖拽。
使用說(shuō)明
需要開啟軟件的分屏權(quán)限,并根據(jù)需求自定義拖拽響應(yīng)。
跨設(shè)備拖拽
將拖拽框架與系統(tǒng)的鍵鼠穿越能力結(jié)合,可以接入跨設(shè)備拖拽,實(shí)現(xiàn)在平板或2in1類型的任意兩臺(tái)設(shè)備之間拖拽數(shù)據(jù)。
使用說(shuō)明
需要滿足跨設(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)站。
常見問(wèn)題-在模擬器中無(wú)法實(shí)現(xiàn)AI識(shí)別拖拽內(nèi)容
問(wèn)題現(xiàn)象
將圖像拖拽至釋放區(qū),無(wú)法識(shí)別圖像中的文字并輸出在釋放區(qū)內(nèi)。
解決措施
模擬器不支持textRecognition接口的調(diào)用,建議使用真機(jī)進(jìn)行調(diào)試,詳細(xì)請(qǐng)參見模擬器與真機(jī)的差異。
-
AI
+關(guān)注
關(guān)注
88文章
37066瀏覽量
290485 -
應(yīng)用程序
+關(guān)注
關(guān)注
38文章
3339瀏覽量
59731 -
HarmonyOS
+關(guān)注
關(guān)注
80文章
2141瀏覽量
35035
原文標(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è)通信問(wèn)題
HarmonyOS開發(fā)文檔(一)

意法半導(dǎo)體與中國(guó)一拖設(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ù)線方案介紹

評(píng)論