1# 拖拽事件 2 3拖拽事件提供了一种通过鼠标或手势触屏传递数据的机制,即从一个组件位置拖出(drag)数据并将其拖入(drop)到另一个组件位置,以触发响应。在这一过程中,拖出方提供数据,而拖入方负责接收和处理数据。这一操作使用户能够便捷地移动、复制或删除指定内容。 4 5## 基本概念 6 7* 拖拽操作:在可响应拖出的组件上长按并滑动以触发拖拽行为,当用户释放手指或鼠标时,拖拽操作即告结束。 8* 拖拽背景(背板):用户拖动数据时的形象化表示。开发者可以通过[onDragStart](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondragstart)的[CustomerBuilder](../reference/apis-arkui/arkui-ts/ts-types.md#custombuilder8)或[DragItemInfo](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragiteminfo说明)进行设置,也可以通过[dragPreview](../reference/apis-arkui/arkui-ts/ts-universal-attributes-drag-drop.md#dragpreview11)通用属性进行自定义。 9* 拖拽内容:被拖动的数据,使用UDMF统一API [UnifiedData](../reference/apis-arkdata/js-apis-data-unifiedDataChannel.md#unifieddata) 进行封装,确保数据的一致性和安全性。 10* 拖出对象:触发拖拽操作并提供数据的组件,通常具有响应拖拽的特性。 11* 拖入目标:可接收并处理拖动数据的组件,能够根据拖入的数据执行相应的操作。 12* 拖拽点:鼠标或手指与屏幕的接触位置,用于判断是否进入组件范围。判定依据是接触点是否位于组件的范围内。 13 14## 拖拽流程 15 16拖拽流程包含手势拖拽流程和鼠标拖拽流程,可帮助开发者理解回调事件触发的时机。 17 18### 手势拖拽流程 19 20对于手势长按触发拖拽的场景,ArkUI在发起拖拽前会校验当前组件是否具备拖拽功能。对于默认可拖出的组件([Search](../reference/apis-arkui/arkui-ts/ts-basic-components-search.md)、[TextInput](../reference/apis-arkui/arkui-ts/ts-basic-components-textinput.md)、[TextArea](../reference/apis-arkui/arkui-ts/ts-basic-components-textarea.md)、[RichEditor](../reference/apis-arkui/arkui-ts/ts-basic-components-richeditor.md)、[Text](../reference/apis-arkui/arkui-ts/ts-basic-components-text.md)、[Image](../reference/apis-arkui/arkui-ts/ts-basic-components-image.md)、<!--Del-->[FormComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-formcomponent-sys.md)、<!--DelEnd-->[Hyperlink](../reference/apis-arkui/arkui-ts/ts-container-hyperlink.md))需要判断是否设置了[draggable](../reference/apis-arkui/arkui-ts/ts-universal-attributes-drag-drop.md#draggable),需检查是否已设置draggable属性为true(若系统使能分层参数,draggable属性默认为true)。其他组件则需额外确认是否已设置onDragStart回调函数。在满足上述条件后,长按时间达到或超过500ms即可触发拖拽,而长按800ms时,系统开始执行预览图的浮起动效。若与Menu功能结合使用,并通过isShow控制其显示与隐藏,建议避免在用户操作800ms后才控制菜单显示,此举可能引发非预期的行为。 21 22手势拖拽(手指/手写笔)触发拖拽流程: 23 24 25 26### 鼠标拖拽流程 27 28鼠标拖拽操作遵循即拖即走的模式,当鼠标左键在可拖拽的组件上按下并移动超过1vp时,即可触发拖拽功能。 29 30当前不仅支持应用内部的拖拽,还支持跨应用的拖拽操作。为了帮助开发者更好地感知拖拽状态并调整系统默认的拖拽行为,ArkUI提供了多个回调事件,具体详情如下: 31 32| **回调事件** | **说明**| 33| ---------------- | ------------------------| 34| [onDragStart](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondragstart) | 拖出的组件产生拖出动作时,该回调触发。<br>该回调可以感知拖拽行为的发起,开发者可以在onDragStart方法中设置拖拽过程中传递的数据,并自定义拖拽的背板图像。建议开发者采用pixelmap的方式来返回背板图像,避免使用customBuilder,因为后者可能会带来额外的性能开销。| 35| [onDragEnter](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondragenter) | 当拖拽操作的拖拽点进入组件的范围时,如果该组件监听了[onDrop](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondrop)事件,此回调将会被触发。| 36| [onDragMove](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondragmove) | 当拖拽点在组件范围内移动时,如果该组件监听了onDrop事件,此回调将会被触发。<br>在这一过程中,可以通过调用[DragEvent](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragevent)中的setResult方法来影响系统在部分场景下的外观表现<br>1. 设置DragResult.DROP\_ENABLED。<br>2. 设置DragResult.DROP\_DISABLED。| 37| [onDragLeave](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondragleave) | 当拖拽点移出组件范围时,如果该组件监听了onDrop事件,此回调将会被触发。<br>在以下两种情况下,系统默认不会触发onDragLeave事件:<br>1. 父组件移动到子组件。<br>2. 目标组件与当前组件布局有重叠。<br>API version 12开始可通过[UIContext](../reference/apis-arkui/js-apis-arkui-UIContext.md)中的[setDragEventStrictReportingEnabled](../reference/apis-arkui/js-apis-arkui-UIContext.md#setdrageventstrictreportingenabled12)方法严格触发onDragLeave事件。| 38| [onDrop](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondrop) | 当用户在组件范围内释放拖拽操作时,此回调会被触发。开发者需在此回调中通过DragEvent的setResult方法来设置拖拽结果,否则在拖出方组件的onDragEnd方法中,通过getResult方法获取的将只是默认的处理结果DragResult.DRAG\_FAILED。<br>此回调是开发者干预系统默认拖入处理行为的关键点,系统会优先执行开发者定义的onDrop回调。通过在onDrop回调中调用setResult方法,开发者可以告知系统如何处理被拖拽的数据。<br>1. 设置 DragResult.DRAG\_SUCCESSFUL,数据完全由开发者自己处理,系统不进行处理。<br>2. 设置DragResult.DRAG\_FAILED,数据不再由系统继续处理。<br>3. 设置DragResult.DRAG\_CANCELED,系统也不需要进行数据处理。<br>4. 设置DragResult.DROP\_ENABLED或DragResult.DROP\_DISABLED会被忽略,等同于设置DragResult.DRAG\_FAILED。| 39| [onDragEnd](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondragend) | 当用户释放拖拽时,拖拽活动终止,发起拖出动作的组件将触发该回调函数。| 40| [onPreDrag](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#onpredrag12) | 当触发拖拽事件的不同阶段时,绑定此事件的组件会触发该回调函数。<br>开发者可利用此方法,在拖拽开始前的不同阶段,根据[PreDragStatus](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#predragstatus12枚举说明)枚举准备相应数据。<br>1. ACTION\_DETECTING\_STATUS:拖拽手势启动阶段。按下50ms时触发。<br>2. READY\_TO\_TRIGGER\_DRAG\_ACTION:拖拽准备完成,可发起拖拽阶段。按下500ms时触发。<br>3. PREVIEW\_LIFT\_STARTED:拖拽浮起动效发起阶段。按下800ms时触发。<br>4. PREVIEW\_LIFT\_FINISHED:拖拽浮起动效结束阶段。浮起动效完全结束时触发。<br>5. PREVIEW\_LANDING\_STARTED:拖拽落回动效发起阶段。落回动效发起时触发。<br>6. PREVIEW\_LANDING\_FINISHED:拖拽落回动效结束阶段。落回动效结束时触发。<br>7. ACTION\_CANCELED\_BEFORE\_DRAG:拖拽浮起落位动效中断。已满足READY_TO_TRIGGER_DRAG_ACTION状态后,未达到动效阶段,手指抬起时触发。| 41 42[DragEvent](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragevent)支持的get方法可用于获取拖拽行为的详细信息,下表展示了在相应的拖拽回调中,这些get方法是否能够返回有效数据。 43| 回调事件 | onDragStart | onDragEnter | onDragMove | onDragLeave | onDrop | onDragEnd | 44| - | - | - | - | - | - | - | 45| getData |—|—|—|—| 支持 |—| 46| getSummary |—| 支持 | 支持 | 支持 | 支持 |—| 47| getResult |—|—|—|—|—| 支持 | 48| getPreviewRect |—|—|—|—| 支持 |—| 49| getVelocity/X/Y |—| 支持 | 支持 | 支持 | 支持 |—| 50| getWindowX/Y | 支持 | 支持 | 支持 | 支持 | 支持 |—| 51| getDisplayX/Y | 支持 | 支持 | 支持 | 支持 | 支持 |—| 52| getX/Y | 支持 | 支持 | 支持 | 支持 | 支持 |—| 53| behavior |—|—|—|—|—| 支持 | 54 55[DragEvent](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragevent)支持相关set方法向系统传递信息,这些信息部分会影响系统对UI或数据的处理方式。下表列出了set方法应该在回调的哪个阶段执行才会被系统接受并处理。 56| 回调事件 | onDragStart | onDragEnter | onDragMove | onDragLeave | onDrop | 57| - | - | - | - | - | - | 58| useCustomDropAnimation |—|—|—|—| 支持 | 59| setData | 支持 |—|—|—|—| 60| setResult | 支持,可通过set failed或cancel来阻止拖拽发起 | 支持,不作为最终结果传递给onDragEnd | 支持,不作为最终结果传递给onDragEnd | 支持,不作为最终结果传递给onDragEnd | 支持,作为最终结果传递给onDragEnd | 61| behavior |—| 支持 | 支持 | 支持 | 支持 | 62 63## 拖拽背板图 64 65在拖拽移动过程中显示的背板图并非组件本身,而是表示用户拖动的数据,开发者可将其设定为任意可显示的图像。具体而言,onDragStart回调中返回的customBuilder或pixelmap可以用于设置拖拽移动过程中的背板图,而浮起图则默认采用组件本身的截图。dragpreview属性中设定的customBuilder或pixelmap可以用于配置浮起和拖拽过程的背板图。若开发者未配置背板图,系统将自动采用组件本身的截图作为拖拽和浮起时的背板图。 66 67拖拽背板图当前支持设置透明度、圆角、阴影和模糊,具体用法见[拖拽控制](../reference/apis-arkui/arkui-ts/ts-universal-attributes-drag-drop.md)。 68 69 70 71**约束限制:** 72 73* 对于容器组件,如果内部内容通过position、offset等接口使得绘制区域超出了容器组件范围,则系统截图无法截取到范围之外的内容。此种情况下,如果一定要浮起,即拖拽背板能够包含范围之外的内容,则可考虑通过扩大容器范围或自定义方式实现。 74* 不论是使用自定义builder或是系统默认截图方式,截图都暂时无法应用[scale](../reference/apis-arkui/arkui-ts/ts-universal-attributes-transformation.md#scale)、[rotate](../reference/apis-arkui/arkui-ts/ts-universal-attributes-transformation.md#rotate)等图形变换效果。 75 76## 通用拖拽适配 77 78如下以[Image](../reference/apis-arkui/arkui-ts/ts-basic-components-image.md)组件为例,介绍组件拖拽开发的基本步骤,以及开发中需要注意的事项。 79 801. 组件使能拖拽。 81 82 设置draggable属性为true,并配置onDragStart回调函数。在回调函数中,可通过UDMF(用户数据管理框架)设置拖拽的数据,并返回自定义的拖拽背景图像。 83 84 ```ts 85 import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData'; 86 87 Image($r('app.media.app_icon')) 88 .width(100) 89 .height(100) 90 .draggable(true) 91 .onDragStart((event) => { 92 let data: unifiedDataChannel.Image = new unifiedDataChannel.Image(); 93 data.imageUri = 'common/pic/img.png'; 94 let unifiedData = new unifiedDataChannel.UnifiedData(data); 95 event.setData(unifiedData); 96 97 let dragItemInfo: DragItemInfo = { 98 pixelMap: this.pixmap, 99 extraInfo: "this is extraInfo", 100 }; 101 // onDragStart回调函数中返回自定义拖拽背板图 102 return dragItemInfo; 103 }) 104 ``` 105 106 手势场景触发的拖拽功能依赖于底层绑定的长按手势。如果开发者在可拖拽组件上也绑定了长按手势,这将与底层的长按手势产生冲突,进而导致拖拽操作失败。为解决此类问题,可以采用并行手势的方案,具体如下。 107 108 ```ts 109 .parallelGesture(LongPressGesture().onAction(() => { 110 promptAction.showToast({ duration: 100, message: 'Long press gesture trigger' }); 111 })) 112 ``` 113 1142. 自定义拖拽背板图。 115 116 可以通过在长按50ms时触发的回调中设置[onPreDrag](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#onpredrag12)回调函数,来提前准备自定义拖拽背板图的pixmap。 117 118 ```ts 119 .onPreDrag((status: PreDragStatus) => { 120 if (preDragStatus == PreDragStatus.ACTION_DETECTING_STATUS) { 121 this.getComponentSnapshot(); 122 } 123 }) 124 ``` 125 126 pixmap的生成可以调用[componentSnapshot.createFromBuilder](../reference/apis-arkui/js-apis-arkui-componentSnapshot.md#componentsnapshotcreatefrombuilder)函数来实现。 127 128 ```ts 129 @Builder 130 pixelMapBuilder() { 131 Column() { 132 Image($r('app.media.startIcon')) 133 .width(120) 134 .height(120) 135 .backgroundColor(Color.Yellow) 136 } 137 } 138 private getComponentSnapshot(): void { 139 this.getUIContext().getComponentSnapshot().createFromBuilder(()=>{this.pixelMapBuilder()}, 140 (error: Error, pixmap: image.PixelMap) => { 141 if(error){ 142 console.log("error: " + JSON.stringify(error)) 143 return; 144 } 145 this.pixmap = pixmap; 146 }) 147 } 148 ``` 149 1503. 若开发者需确保触发[onDragLeave](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondragleave)事件,应通过调用[setDragEventStrictReportingEnabled](../reference/apis-arkui/js-apis-arkui-UIContext.md#setdrageventstrictreportingenabled12)方法进行设置。 151 152 ```ts 153 import { UIAbility } from '@kit.AbilityKit'; 154 import { window, UIContext } from '@kit.ArkUI'; 155 156 export default class EntryAbility extends UIAbility { 157 onWindowStageCreate(windowStage: window.WindowStage): void { 158 windowStage.loadContent('pages/Index', (err, data) => { 159 if (err.code) { 160 return; 161 } 162 windowStage.getMainWindow((err, data) => { 163 if (err.code) { 164 return; 165 } 166 let windowClass: window.Window = data; 167 let uiContext: UIContext = windowClass.getUIContext(); 168 uiContext.getDragController().setDragEventStrictReportingEnabled(true); 169 }); 170 }); 171 } 172 } 173 ``` 174 1754. 拖拽过程显示角标样式。 176 177 通过设置[allowDrop](../reference/apis-arkui/arkui-ts/ts-universal-attributes-drag-drop.md#allowdrop)来定义接收的数据类型,这将影响角标显示。当拖拽的数据符合定义的允许落入的数据类型时,显示“COPY”角标。当拖拽的数据类型不在允许范围内时,显示“FORBIDDEN”角标。若未设置allowDrop,则显示“MOVE”角标。以下代码示例表示仅接收UnifiedData中定义的HYPERLINK和PLAIN\_TEXT类型数据,其他类型数据将被禁止落入。 178 179 ```ts 180 .allowDrop([uniformTypeDescriptor.UniformDataType.HYPERLINK, uniformTypeDescriptor.UniformDataType.PLAIN_TEXT]) 181 ``` 182 183 在实现onDrop回调的情况下,还可以通过在onDragMove中设置[DragResult](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragresult10枚举说明)为DROP_ENABLED,并将[DragBehavior](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragbehavior10)设置为COPY或MOVE,以此来控制角标显示。如下代码将移动时的角标强制设置为“MOVE”。 184 185 ```ts 186 .onDragMove((event) => { 187 event.setResult(DragResult.DROP_ENABLED); 188 event.dragBehavior = DragBehavior.MOVE; 189 }) 190 ``` 191 1925. 拖拽数据的接收。 193 194 需要设置onDrop回调函数,并在回调函数中处理拖拽数据,显示设置拖拽结果。 195 196 ```ts 197 .onDrop((dragEvent?: DragEvent) => { 198 // 获取拖拽数据 199 this.getDataFromUdmf((dragEvent as DragEvent), (event: DragEvent) => { 200 let records: Array<unifiedDataChannel.UnifiedRecord> = event.getData().getRecords(); 201 let rect: Rectangle = event.getPreviewRect(); 202 this.imageWidth = Number(rect.width); 203 this.imageHeight = Number(rect.height); 204 this.targetImage = (records[0] as unifiedDataChannel.Image).imageUri; 205 this.imgState = Visibility.None; 206 // 显式设置result为successful,则将该值传递给拖出方的onDragEnd 207 event.setResult(DragResult.DRAG_SUCCESSFUL); 208 }) 209 ``` 210 211 数据的传递是通过UDMF实现的,在数据较大时可能存在时延,因此在首次获取数据失败时建议加1500ms的延迟重试机制。 212 213 ```ts 214 getDataFromUdmfRetry(event: DragEvent, callback: (data: DragEvent) => void) { 215 try { 216 let data: UnifiedData = event.getData(); 217 if (!data) { 218 return false; 219 } 220 let records: Array<unifiedDataChannel.UnifiedRecord> = data.getRecords(); 221 if (!records || records.length <= 0) { 222 return false; 223 } 224 callback(event); 225 return true; 226 } catch (e) { 227 console.log("getData failed, code: " + (e as BusinessError).code + ", message: " + (e as BusinessError).message); 228 return false; 229 } 230 } 231 232 getDataFromUdmf(event: DragEvent, callback: (data: DragEvent) => void) { 233 if (this.getDataFromUdmfRetry(event, callback)) { 234 return; 235 } 236 setTimeout(() => { 237 this.getDataFromUdmfRetry(event, callback); 238 }, 1500); 239 } 240 ``` 241 2426. 拖拽发起方可以通过设置onDragEnd回调感知拖拽结果。 243 244 ```ts 245 import { promptAction } from '@kit.ArkUI'; 246 247 .onDragEnd((event) => { 248 // onDragEnd里取到的result值在接收方onDrop设置 249 if (event.getResult() === DragResult.DRAG_SUCCESSFUL) { 250 promptAction.showToast({ duration: 100, message: 'Drag Success' }); 251 } else if (event.getResult() === DragResult.DRAG_FAILED) { 252 promptAction.showToast({ duration: 100, message: 'Drag failed' }); 253 } 254 }) 255 ``` 256 257**完整示例:** 258 259```ts 260import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData'; 261import { promptAction } from '@kit.ArkUI'; 262import { BusinessError } from '@kit.BasicServicesKit'; 263import { image } from '@kit.ImageKit'; 264 265@Entry 266@Component 267struct Index { 268 @State targetImage: string = ''; 269 @State imageWidth: number = 100; 270 @State imageHeight: number = 100; 271 @State imgState: Visibility = Visibility.Visible; 272 @State pixmap: image.PixelMap|undefined = undefined 273 274 @Builder 275 pixelMapBuilder() { 276 Column() { 277 Image($r('app.media.startIcon')) 278 .width(120) 279 .height(120) 280 .backgroundColor(Color.Yellow) 281 } 282 } 283 284 getDataFromUdmfRetry(event: DragEvent, callback: (data: DragEvent) => void) { 285 try { 286 let data: UnifiedData = event.getData(); 287 if (!data) { 288 return false; 289 } 290 let records: Array<unifiedDataChannel.UnifiedRecord> = data.getRecords(); 291 if (!records || records.length <= 0) { 292 return false; 293 } 294 callback(event); 295 return true; 296 } catch (e) { 297 console.log("getData failed, code: " + (e as BusinessError).code + ", message: " + (e as BusinessError).message); 298 return false; 299 } 300 } 301 // 获取UDMF数据,首次获取失败后添加1500ms延迟重试机制 302 getDataFromUdmf(event: DragEvent, callback: (data: DragEvent) => void) { 303 if (this.getDataFromUdmfRetry(event, callback)) { 304 return; 305 } 306 setTimeout(() => { 307 this.getDataFromUdmfRetry(event, callback); 308 }, 1500); 309 } 310 // 调用componentSnapshot中的createFromBuilder接口截取自定义builder的截图 311 private getComponentSnapshot(): void { 312 this.getUIContext().getComponentSnapshot().createFromBuilder(()=>{this.pixelMapBuilder()}, 313 (error: Error, pixmap: image.PixelMap) => { 314 if(error){ 315 console.log("error: " + JSON.stringify(error)) 316 return; 317 } 318 this.pixmap = pixmap; 319 }) 320 } 321 // 长按50ms时提前准备自定义截图的pixmap 322 private PreDragChange(preDragStatus: PreDragStatus): void { 323 if (preDragStatus == PreDragStatus.ACTION_DETECTING_STATUS) { 324 this.getComponentSnapshot(); 325 } 326 } 327 328 build() { 329 Row() { 330 Column() { 331 Text('start Drag') 332 .fontSize(18) 333 .width('100%') 334 .height(40) 335 .margin(10) 336 .backgroundColor('#008888') 337 Row() { 338 Image($r('app.media.app_icon')) 339 .width(100) 340 .height(100) 341 .draggable(true) 342 .margin({ left: 15 }) 343 .visibility(this.imgState) 344 // 绑定平行手势,可同时触发应用自定义长按手势 345 .parallelGesture(LongPressGesture().onAction(() => { 346 promptAction.showToast({ duration: 100, message: 'Long press gesture trigger' }); 347 })) 348 .onDragStart((event) => { 349 let data: unifiedDataChannel.Image = new unifiedDataChannel.Image(); 350 data.imageUri = 'common/pic/img.png'; 351 let unifiedData = new unifiedDataChannel.UnifiedData(data); 352 event.setData(unifiedData); 353 354 let dragItemInfo: DragItemInfo = { 355 pixelMap: this.pixmap, 356 extraInfo: "this is extraInfo", 357 }; 358 return dragItemInfo; 359 }) 360 // 提前准备拖拽自定义背板图 361 .onPreDrag((status: PreDragStatus) => { 362 this.PreDragChange(status); 363 }) 364 .onDragEnd((event) => { 365 // onDragEnd里取到的result值在接收方onDrop设置 366 if (event.getResult() === DragResult.DRAG_SUCCESSFUL) { 367 promptAction.showToast({ duration: 100, message: 'Drag Success' }); 368 } else if (event.getResult() === DragResult.DRAG_FAILED) { 369 promptAction.showToast({ duration: 100, message: 'Drag failed' }); 370 } 371 }) 372 } 373 374 Text('Drag Target Area') 375 .fontSize(20) 376 .width('100%') 377 .height(40) 378 .margin(10) 379 .backgroundColor('#008888') 380 Row() { 381 Image(this.targetImage) 382 .width(this.imageWidth) 383 .height(this.imageHeight) 384 .draggable(true) 385 .margin({ left: 15 }) 386 .border({ color: Color.Black, width: 1 }) 387 // 控制角标显示类型为MOVE,即不显示角标 388 .onDragMove((event) => { 389 event.setResult(DragResult.DROP_ENABLED) 390 event.dragBehavior = DragBehavior.MOVE 391 }) 392 .allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE]) 393 .onDrop((dragEvent?: DragEvent) => { 394 // 获取拖拽数据 395 this.getDataFromUdmf((dragEvent as DragEvent), (event: DragEvent) => { 396 let records: Array<unifiedDataChannel.UnifiedRecord> = event.getData().getRecords(); 397 let rect: Rectangle = event.getPreviewRect(); 398 this.imageWidth = Number(rect.width); 399 this.imageHeight = Number(rect.height); 400 this.targetImage = (records[0] as unifiedDataChannel.Image).imageUri; 401 this.imgState = Visibility.None; 402 // 显式设置result为successful,则将该值传递给拖出方的onDragEnd 403 event.setResult(DragResult.DRAG_SUCCESSFUL); 404 }) 405 }) 406 } 407 } 408 .width('100%') 409 .height('100%') 410 } 411 .height('100%') 412 } 413} 414 415``` 416 417## 多选拖拽适配 418 419从API version 12开始,[Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md)组件和[List](../reference/apis-arkui/arkui-ts/ts-container-list.md)组件中的GridItem和ListItem组件支持多选与拖拽功能。目前,仅支持onDragStart的触发方式。 420 421以下以Grid为例,详细介绍实现多选拖拽的基本步骤,以及在开发过程中需要注意的事项。 422 4231. 组件多选拖拽使能。 424 425 创建GridItem子组件并绑定onDragStart回调函数。同时设置GridItem组件的状态为可选中。 426 427 ```ts 428 Grid() { 429 ForEach(this.numbers, (idx: number) => { 430 GridItem() { 431 Column() 432 .backgroundColor(this.colors[idx % 9]) 433 .width(50) 434 .height(50) 435 .opacity(1.0) 436 .id('grid'+idx) 437 } 438 .onDragStart(()=>{}) 439 .selectable(true) 440 }, (idx: string) => idx) 441 } 442 ``` 443 444 多选拖拽功能默认处于关闭状态。若要启用此功能,需在[dragPreviewOptions](../reference/apis-arkui/arkui-ts/ts-universal-attributes-drag-drop.md#dragpreviewoptions11)接口的DragInteractionOptions参数中,将isMultiSelectionEnabled设置为true,以表明当前组件支持多选。此外,DragInteractionOptions还包含defaultAnimationBeforeLifting参数,用于控制组件浮起前的默认效果。将该参数设置为true,组件在浮起前将展示一个默认的缩小动画效果。 445 446 ```ts 447 .dragPreviewOptions({isMultiSelectionEnabled:true,defaultAnimationBeforeLifting:true}) 448 ``` 449 450 为了确保选中状态,应将GridItem子组件的selected属性设置为true。例如,可以通过调用[onClick](../reference/apis-arkui/arkui-ts/ts-universal-events-click.md#onclick)来设置特定组件为选中状态。 451 452 ```ts 453 .selected(this.isSelectedGrid[idx]) 454 .onClick(()=>{ 455 this.isSelectedGrid[idx] = !this.isSelectedGrid[idx] 456 }) 457 ``` 458 4592. 优化多选拖拽性能。 460 461 在多选拖拽操作中,当多选触发聚拢动画效果时,系统会截取当前屏幕内显示的选中组件图像。如果选中组件数量过多,可能会造成较高的性能消耗。为了优化性能,多选拖拽功能支持从dragPreview中获取截图,用以实现聚拢动画效果,从而有效节省系统资源。 462 463 ```ts 464 .dragPreview({ 465 pixelMap:this.pixmap 466 }) 467 ``` 468 469 截图的获取可以在选中组件时通过调用componentSnapshot中的[get](../reference/apis-arkui/js-apis-arkui-componentSnapshot.md#componentsnapshotget-1)方法获取。以下示例通过获取组件对应id的方法进行截图。 470 471 ```ts 472 @State previewData: DragItemInfo[] = [] 473 @State isSelectedGrid: boolean[] = [] 474 .onClick(()=>{ 475 this.isSelectedGrid[idx] = !this.isSelectedGrid[idx] 476 if (this.isSelectedGrid[idx]) { 477 let gridItemName = 'grid' + idx 478 this.getUIContext().getComponentSnapshot().get(gridItemName, (error: Error, pixmap: image.PixelMap)=>{ 479 this.pixmap = pixmap 480 this.previewData[idx] = { 481 pixelMap:this.pixmap 482 } 483 }) 484 } 485 }) 486 ``` 487 4883. 多选显示效果。 489 490 通过[stateStyles](../reference/apis-arkui/arkui-ts/ts-universal-attributes-polymorphic-style.md#statestyles)可以设置选中态和非选中态的显示效果,方便区分。 491 492 ```ts 493 @Styles 494 normalStyles(): void{ 495 .opacity(1.0) 496 } 497 498 @Styles 499 selectStyles(): void{ 500 .opacity(0.4) 501 } 502 503 .stateStyles({ 504 normal : this.normalStyles, 505 selected: this.selectStyles 506 }) 507 ``` 508 5094. 适配数量角标。 510 511 多选拖拽的数量角标当前需要应用使用dragPreviewOptions中的numberBadge参数设置,开发者需要根据当前选中的节点数量来设置数量角标。 512 513 ```ts 514 @State numberBadge: number = 0; 515 516 .onClick(()=>{ 517 this.isSelectedGrid[idx] = !this.isSelectedGrid[idx] 518 if (this.isSelectedGrid[idx]) { 519 this.numberBadge++; 520 } else { 521 this.numberBadge--; 522 } 523 }) 524 // 多选场景右上角数量角标需要应用设置numberBadge参数 525 .dragPreviewOptions({numberBadge: this.numberBadge}) 526 ``` 527 528**完整示例:** 529 530```ts 531import { image } from '@kit.ImageKit'; 532 533@Entry 534@Component 535struct GridEts { 536 @State pixmap: image.PixelMap|undefined = undefined 537 @State numbers: number[] = [] 538 @State isSelectedGrid: boolean[] = [] 539 @State previewData: DragItemInfo[] = [] 540 @State colors: Color[] = [Color.Red, Color.Blue, Color.Brown, Color.Gray, Color.Green, Color.Grey, Color.Orange,Color.Pink ,Color.Yellow] 541 @State numberBadge: number = 0; 542 543 @Styles 544 normalStyles(): void{ 545 .opacity(1.0) 546 } 547 548 @Styles 549 selectStyles(): void{ 550 .opacity(0.4) 551 } 552 553 onPageShow(): void { 554 let i: number = 0 555 for(i=0;i<100;i++){ 556 this.numbers.push(i) 557 this.isSelectedGrid.push(false) 558 this.previewData.push({}) 559 } 560 } 561 562 @Builder 563 RandomBuilder(idx: number) { 564 Column() 565 .backgroundColor(this.colors[idx % 9]) 566 .width(50) 567 .height(50) 568 .opacity(1.0) 569 } 570 571 build() { 572 Column({ space: 5 }) { 573 Grid() { 574 ForEach(this.numbers, (idx: number) => { 575 GridItem() { 576 Column() 577 .backgroundColor(this.colors[idx % 9]) 578 .width(50) 579 .height(50) 580 .opacity(1.0) 581 .id('grid'+idx) 582 } 583 .dragPreview(this.previewData[idx]) 584 .selectable(true) 585 .selected(this.isSelectedGrid[idx]) 586 // 设置多选显示效果 587 .stateStyles({ 588 normal : this.normalStyles, 589 selected: this.selectStyles 590 }) 591 .onClick(()=>{ 592 this.isSelectedGrid[idx] = !this.isSelectedGrid[idx] 593 if (this.isSelectedGrid[idx]) { 594 this.numberBadge++; 595 let gridItemName = 'grid' + idx 596 // 选中状态下提前调用componentSnapshot中的get接口获取pixmap 597 this.getUIContext().getComponentSnapshot().get(gridItemName, (error: Error, pixmap: image.PixelMap)=>{ 598 this.pixmap = pixmap 599 this.previewData[idx] = { 600 pixelMap:this.pixmap 601 } 602 }) 603 } else { 604 this.numberBadge--; 605 } 606 }) 607 // 使能多选拖拽,右上角数量角标需要应用设置numberBadge参数 608 .dragPreviewOptions({numberBadge: this.numberBadge},{isMultiSelectionEnabled:true,defaultAnimationBeforeLifting:true}) 609 .onDragStart(()=>{ 610 }) 611 }, (idx: string) => idx) 612 } 613 .columnsTemplate('1fr 1fr 1fr 1fr 1fr') 614 .columnsGap(5) 615 .rowsGap(10) 616 .backgroundColor(0xFAEEE0) 617 }.width('100%').margin({ top: 5 }) 618 } 619} 620``` 621<!--RP1--><!--RP1End--> 622