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### 通用拖拽适配 79 80如下以[Image](../reference/apis-arkui/arkui-ts/ts-basic-components-image.md)组件为例,介绍组件拖拽开发的基本步骤,以及开发中需要注意的事项。 81 821. 组件使能拖拽。 83 84 设置draggable属性为true,并配置onDragStart回调函数。在回调函数中,可通过UDMF(用户数据管理框架)设置拖拽的数据,并返回自定义的拖拽背景图像。 85 86 ```ts 87 import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData'; 88 89 Image($r('app.media.app_icon')) 90 .width(100) 91 .height(100) 92 .draggable(true) 93 .onDragStart((event) => { 94 let data: unifiedDataChannel.Image = new unifiedDataChannel.Image(); 95 data.imageUri = 'common/pic/img.png'; 96 let unifiedData = new unifiedDataChannel.UnifiedData(data); 97 event.setData(unifiedData); 98 99 let dragItemInfo: DragItemInfo = { 100 pixelMap: this.pixmap, 101 extraInfo: "this is extraInfo", 102 }; 103 // onDragStart回调函数中返回自定义拖拽背板图 104 return dragItemInfo; 105 }) 106 ``` 107 108 手势场景触发的拖拽功能依赖于底层绑定的长按手势。如果开发者在可拖拽组件上也绑定了长按手势,这将与底层的长按手势产生冲突,进而导致拖拽操作失败。为解决此类问题,可以采用并行手势的方案,具体如下。 109 110 ```ts 111 .parallelGesture(LongPressGesture().onAction(() => { 112 this.getUIContext().getPromptAction().showToast({ duration: 100, message: 'Long press gesture trigger' }); 113 })) 114 ``` 115 1162. 自定义拖拽背板图。 117 118 可以通过在长按50ms时触发的回调中设置[onPreDrag](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#onpredrag12)回调函数,来提前准备自定义拖拽背板图的pixmap。 119 120 ```ts 121 .onPreDrag((status: PreDragStatus) => { 122 if (preDragStatus == PreDragStatus.ACTION_DETECTING_STATUS) { 123 this.getComponentSnapshot(); 124 } 125 }) 126 ``` 127 128 pixmap的生成可以调用[componentSnapshot.createFromBuilder](../reference/apis-arkui/js-apis-arkui-componentSnapshot.md#componentsnapshotcreatefrombuilder)函数来实现。 129 130 ```ts 131 @Builder 132 pixelMapBuilder() { 133 Column() { 134 Image($r('app.media.startIcon')) 135 .width(120) 136 .height(120) 137 .backgroundColor(Color.Yellow) 138 } 139 } 140 private getComponentSnapshot(): void { 141 this.getUIContext().getComponentSnapshot().createFromBuilder(()=>{this.pixelMapBuilder()}, 142 (error: Error, pixmap: image.PixelMap) => { 143 if(error){ 144 console.log("error: " + JSON.stringify(error)) 145 return; 146 } 147 this.pixmap = pixmap; 148 }) 149 } 150 ``` 151 1523. 若开发者需确保触发[onDragLeave](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondragleave)事件,应通过调用[setDragEventStrictReportingEnabled](../reference/apis-arkui/js-apis-arkui-UIContext.md#setdrageventstrictreportingenabled12)方法进行设置。 153 154 ```ts 155 import { UIAbility } from '@kit.AbilityKit'; 156 import { window, UIContext } from '@kit.ArkUI'; 157 158 export default class EntryAbility extends UIAbility { 159 onWindowStageCreate(windowStage: window.WindowStage): void { 160 windowStage.loadContent('pages/Index', (err, data) => { 161 if (err.code) { 162 return; 163 } 164 windowStage.getMainWindow((err, data) => { 165 if (err.code) { 166 return; 167 } 168 let windowClass: window.Window = data; 169 let uiContext: UIContext = windowClass.getUIContext(); 170 uiContext.getDragController().setDragEventStrictReportingEnabled(true); 171 }); 172 }); 173 } 174 } 175 ``` 176 1774. 拖拽过程显示角标样式。 178 179 通过设置[allowDrop](../reference/apis-arkui/arkui-ts/ts-universal-attributes-drag-drop.md#allowdrop)来定义接收的数据类型,这将影响角标显示。当拖拽的数据符合定义的允许落入的数据类型时,显示“COPY”角标。当拖拽的数据类型不在允许范围内时,显示“FORBIDDEN”角标。若未设置allowDrop,则显示“MOVE”角标。以下代码示例表示仅接收UnifiedData中定义的HYPERLINK和PLAIN\_TEXT类型数据,其他类型数据将被禁止落入。 180 181 ```ts 182 .allowDrop([uniformTypeDescriptor.UniformDataType.HYPERLINK, uniformTypeDescriptor.UniformDataType.PLAIN_TEXT]) 183 ``` 184 185 在实现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”。 186 187 ```ts 188 .onDragMove((event) => { 189 event.setResult(DragResult.DROP_ENABLED); 190 event.dragBehavior = DragBehavior.MOVE; 191 }) 192 ``` 193 1945. 拖拽数据的接收。 195 196 需要设置onDrop回调函数,并在回调函数中处理拖拽数据,显示设置拖拽结果。 197 198 ```ts 199 .onDrop((dragEvent?: DragEvent) => { 200 // 获取拖拽数据 201 this.getDataFromUdmf((dragEvent as DragEvent), (event: DragEvent) => { 202 let records: Array<unifiedDataChannel.UnifiedRecord> = event.getData().getRecords(); 203 let rect: Rectangle = event.getPreviewRect(); 204 this.imageWidth = Number(rect.width); 205 this.imageHeight = Number(rect.height); 206 this.targetImage = (records[0] as unifiedDataChannel.Image).imageUri; 207 this.imgState = Visibility.None; 208 // 显式设置result为successful,则将该值传递给拖出方的onDragEnd 209 event.setResult(DragResult.DRAG_SUCCESSFUL); 210 }) 211 ``` 212 213 数据的传递是通过UDMF实现的,在数据较大时可能存在时延,因此在首次获取数据失败时建议加1500ms的延迟重试机制。 214 215 ```ts 216 getDataFromUdmfRetry(event: DragEvent, callback: (data: DragEvent) => void) { 217 try { 218 let data: UnifiedData = event.getData(); 219 if (!data) { 220 return false; 221 } 222 let records: Array<unifiedDataChannel.UnifiedRecord> = data.getRecords(); 223 if (!records || records.length <= 0) { 224 return false; 225 } 226 callback(event); 227 return true; 228 } catch (e) { 229 console.log("getData failed, code: " + (e as BusinessError).code + ", message: " + (e as BusinessError).message); 230 return false; 231 } 232 } 233 234 getDataFromUdmf(event: DragEvent, callback: (data: DragEvent) => void) { 235 if (this.getDataFromUdmfRetry(event, callback)) { 236 return; 237 } 238 setTimeout(() => { 239 this.getDataFromUdmfRetry(event, callback); 240 }, 1500); 241 } 242 ``` 243 2446. 拖拽发起方可以通过设置onDragEnd回调感知拖拽结果。 245 246 ```ts 247 import { promptAction } from '@kit.ArkUI'; 248 249 .onDragEnd((event) => { 250 // onDragEnd里取到的result值在接收方onDrop设置 251 if (event.getResult() === DragResult.DRAG_SUCCESSFUL) { 252 this.getUIContext().getPromptAction().showToast({ duration: 100, message: 'Drag Success' }); 253 } else if (event.getResult() === DragResult.DRAG_FAILED) { 254 this.getUIContext().getPromptAction().showToast({ duration: 100, message: 'Drag failed' }); 255 } 256 }) 257 ``` 258 259**完整示例:** 260 261```ts 262import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData'; 263import { promptAction } from '@kit.ArkUI'; 264import { BusinessError } from '@kit.BasicServicesKit'; 265import { image } from '@kit.ImageKit'; 266 267@Entry 268@Component 269struct Index { 270 @State targetImage: string = ''; 271 @State imageWidth: number = 100; 272 @State imageHeight: number = 100; 273 @State imgState: Visibility = Visibility.Visible; 274 @State pixmap: image.PixelMap|undefined = undefined 275 276 @Builder 277 pixelMapBuilder() { 278 Column() { 279 Image($r('app.media.startIcon')) 280 .width(120) 281 .height(120) 282 .backgroundColor(Color.Yellow) 283 } 284 } 285 286 getDataFromUdmfRetry(event: DragEvent, callback: (data: DragEvent) => void) { 287 try { 288 let data: UnifiedData = event.getData(); 289 if (!data) { 290 return false; 291 } 292 let records: Array<unifiedDataChannel.UnifiedRecord> = data.getRecords(); 293 if (!records || records.length <= 0) { 294 return false; 295 } 296 callback(event); 297 return true; 298 } catch (e) { 299 console.log("getData failed, code: " + (e as BusinessError).code + ", message: " + (e as BusinessError).message); 300 return false; 301 } 302 } 303 // 获取UDMF数据,首次获取失败后添加1500ms延迟重试机制 304 getDataFromUdmf(event: DragEvent, callback: (data: DragEvent) => void) { 305 if (this.getDataFromUdmfRetry(event, callback)) { 306 return; 307 } 308 setTimeout(() => { 309 this.getDataFromUdmfRetry(event, callback); 310 }, 1500); 311 } 312 // 调用componentSnapshot中的createFromBuilder接口截取自定义builder的截图 313 private getComponentSnapshot(): void { 314 this.getUIContext().getComponentSnapshot().createFromBuilder(()=>{this.pixelMapBuilder()}, 315 (error: Error, pixmap: image.PixelMap) => { 316 if(error){ 317 console.log("error: " + JSON.stringify(error)) 318 return; 319 } 320 this.pixmap = pixmap; 321 }) 322 } 323 // 长按50ms时提前准备自定义截图的pixmap 324 private PreDragChange(preDragStatus: PreDragStatus): void { 325 if (preDragStatus == PreDragStatus.ACTION_DETECTING_STATUS) { 326 this.getComponentSnapshot(); 327 } 328 } 329 330 build() { 331 Row() { 332 Column() { 333 Text('start Drag') 334 .fontSize(18) 335 .width('100%') 336 .height(40) 337 .margin(10) 338 .backgroundColor('#008888') 339 Row() { 340 Image($r('app.media.app_icon')) 341 .width(100) 342 .height(100) 343 .draggable(true) 344 .margin({ left: 15 }) 345 .visibility(this.imgState) 346 // 绑定平行手势,可同时触发应用自定义长按手势 347 .parallelGesture(LongPressGesture().onAction(() => { 348 this.getUIContext().getPromptAction().showToast({ duration: 100, message: 'Long press gesture trigger' }); 349 })) 350 .onDragStart((event) => { 351 let data: unifiedDataChannel.Image = new unifiedDataChannel.Image(); 352 data.imageUri = 'common/pic/img.png'; 353 let unifiedData = new unifiedDataChannel.UnifiedData(data); 354 event.setData(unifiedData); 355 356 let dragItemInfo: DragItemInfo = { 357 pixelMap: this.pixmap, 358 extraInfo: "this is extraInfo", 359 }; 360 return dragItemInfo; 361 }) 362 // 提前准备拖拽自定义背板图 363 .onPreDrag((status: PreDragStatus) => { 364 this.PreDragChange(status); 365 }) 366 .onDragEnd((event) => { 367 // onDragEnd里取到的result值在接收方onDrop设置 368 if (event.getResult() === DragResult.DRAG_SUCCESSFUL) { 369 this.getUIContext().getPromptAction().showToast({ duration: 100, message: 'Drag Success' }); 370 } else if (event.getResult() === DragResult.DRAG_FAILED) { 371 this.getUIContext().getPromptAction().showToast({ duration: 100, message: 'Drag failed' }); 372 } 373 }) 374 } 375 376 Text('Drag Target Area') 377 .fontSize(20) 378 .width('100%') 379 .height(40) 380 .margin(10) 381 .backgroundColor('#008888') 382 Row() { 383 Image(this.targetImage) 384 .width(this.imageWidth) 385 .height(this.imageHeight) 386 .draggable(true) 387 .margin({ left: 15 }) 388 .border({ color: Color.Black, width: 1 }) 389 // 控制角标显示类型为MOVE,即不显示角标 390 .onDragMove((event) => { 391 event.setResult(DragResult.DROP_ENABLED) 392 event.dragBehavior = DragBehavior.MOVE 393 }) 394 .allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE]) 395 .onDrop((dragEvent?: DragEvent) => { 396 // 获取拖拽数据 397 this.getDataFromUdmf((dragEvent as DragEvent), (event: DragEvent) => { 398 let records: Array<unifiedDataChannel.UnifiedRecord> = event.getData().getRecords(); 399 let rect: Rectangle = event.getPreviewRect(); 400 this.imageWidth = Number(rect.width); 401 this.imageHeight = Number(rect.height); 402 this.targetImage = (records[0] as unifiedDataChannel.Image).imageUri; 403 this.imgState = Visibility.None; 404 // 显式设置result为successful,则将该值传递给拖出方的onDragEnd 405 event.setResult(DragResult.DRAG_SUCCESSFUL); 406 }) 407 }) 408 } 409 } 410 .width('100%') 411 .height('100%') 412 } 413 .height('100%') 414 } 415} 416 417``` 418 419 420### 多选拖拽适配 421 422从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的触发方式。 423 424以下以Grid为例,详细介绍实现多选拖拽的基本步骤,以及在开发过程中需要注意的事项。 425 4261. 组件多选拖拽使能。 427 428 创建GridItem子组件并绑定onDragStart回调函数。同时设置GridItem组件的状态为可选中。 429 430 ```ts 431 Grid() { 432 ForEach(this.numbers, (idx: number) => { 433 GridItem() { 434 Column() 435 .backgroundColor(Color.Blue) 436 .width(50) 437 .height(50) 438 .opacity(1.0) 439 .id('grid'+idx) 440 } 441 .onDragStart(()=>{}) 442 .selectable(true) 443 }, (idx: string) => idx) 444 } 445 ``` 446 447 多选拖拽功能默认处于关闭状态。若要启用此功能,需在[dragPreviewOptions](../reference/apis-arkui/arkui-ts/ts-universal-attributes-drag-drop.md#dragpreviewoptions11)接口的DragInteractionOptions参数中,将isMultiSelectionEnabled设置为true,以表明当前组件支持多选。此外,DragInteractionOptions还包含defaultAnimationBeforeLifting参数,用于控制组件浮起前的默认效果。将该参数设置为true,组件在浮起前将展示一个默认的缩小动画效果。 448 449 ```ts 450 .dragPreviewOptions({isMultiSelectionEnabled:true,defaultAnimationBeforeLifting:true}) 451 ``` 452 453 为了确保选中状态,应将GridItem子组件的selected属性设置为true。例如,可以通过调用[onClick](../reference/apis-arkui/arkui-ts/ts-universal-events-click.md#onclick)来设置特定组件为选中状态。 454 455 ```ts 456 .selected(this.isSelectedGrid[idx]) 457 .onClick(()=>{ 458 this.isSelectedGrid[idx] = !this.isSelectedGrid[idx] 459 }) 460 ``` 461 4622. 优化多选拖拽性能。 463 464 在多选拖拽操作中,当多选触发聚拢动画效果时,系统会截取当前屏幕内显示的选中组件图像。如果选中组件数量过多,可能会造成较高的性能消耗。为了优化性能,多选拖拽功能支持从dragPreview中获取截图,用以实现聚拢动画效果,从而有效节省系统资源。 465 466 ```ts 467 .dragPreview({ 468 pixelMap:this.pixmap 469 }) 470 ``` 471 472 截图的获取可以在选中组件时通过调用componentSnapshot中的[get](../reference/apis-arkui/js-apis-arkui-componentSnapshot.md#componentsnapshotget-1)方法获取。以下示例通过获取组件对应id的方法进行截图。 473 474 ```ts 475 @State previewData: DragItemInfo[] = [] 476 @State isSelectedGrid: boolean[] = [] 477 .onClick(()=>{ 478 this.isSelectedGrid[idx] = !this.isSelectedGrid[idx] 479 if (this.isSelectedGrid[idx]) { 480 let gridItemName = 'grid' + idx 481 this.getUIContext().getComponentSnapshot().get(gridItemName, (error: Error, pixmap: image.PixelMap)=>{ 482 this.pixmap = pixmap 483 this.previewData[idx] = { 484 pixelMap:this.pixmap 485 } 486 }) 487 } 488 }) 489 ``` 490 4913. 多选显示效果。 492 493 通过[stateStyles](../reference/apis-arkui/arkui-ts/ts-universal-attributes-polymorphic-style.md#statestyles)可以设置选中态和非选中态的显示效果,方便区分。 494 495 ```ts 496 @Styles 497 normalStyles(): void{ 498 .opacity(1.0) 499 } 500 501 @Styles 502 selectStyles(): void{ 503 .opacity(0.4) 504 } 505 506 .stateStyles({ 507 normal : this.normalStyles, 508 selected: this.selectStyles 509 }) 510 ``` 511 5124. 适配数量角标。 513 514 多选拖拽的数量角标当前需要应用使用dragPreviewOptions中的numberBadge参数设置,开发者需要根据当前选中的节点数量来设置数量角标。 515 516 ```ts 517 @State numberBadge: number = 0; 518 519 .onClick(()=>{ 520 this.isSelectedGrid[idx] = !this.isSelectedGrid[idx] 521 if (this.isSelectedGrid[idx]) { 522 this.numberBadge++; 523 } else { 524 this.numberBadge--; 525 } 526 }) 527 // 多选场景右上角数量角标需要应用设置numberBadge参数 528 .dragPreviewOptions({numberBadge: this.numberBadge}) 529 ``` 530 531**完整示例:** 532 533```ts 534import { image } from '@kit.ImageKit'; 535 536@Entry 537@Component 538struct GridEts { 539 @State pixmap: image.PixelMap|undefined = undefined 540 @State numbers: number[] = [] 541 @State isSelectedGrid: boolean[] = [] 542 @State previewData: DragItemInfo[] = [] 543 @State numberBadge: number = 0; 544 545 @Styles 546 normalStyles(): void{ 547 .opacity(1.0) 548 } 549 550 @Styles 551 selectStyles(): void{ 552 .opacity(0.4) 553 } 554 555 onPageShow(): void { 556 let i: number = 0 557 for(i=0;i<100;i++){ 558 this.numbers.push(i) 559 this.isSelectedGrid.push(false) 560 this.previewData.push({}) 561 } 562 } 563 564 @Builder 565 RandomBuilder(idx: number) { 566 Column() 567 .backgroundColor(Color.Blue) 568 .width(50) 569 .height(50) 570 .opacity(1.0) 571 } 572 573 build() { 574 Column({ space: 5 }) { 575 Grid() { 576 ForEach(this.numbers, (idx: number) => { 577 GridItem() { 578 Column() 579 .backgroundColor(Color.Blue) 580 .width(50) 581 .height(50) 582 .opacity(1.0) 583 .id('grid'+idx) 584 } 585 .dragPreview(this.previewData[idx]) 586 .selectable(true) 587 .selected(this.isSelectedGrid[idx]) 588 // 设置多选显示效果 589 .stateStyles({ 590 normal : this.normalStyles, 591 selected: this.selectStyles 592 }) 593 .onClick(()=>{ 594 this.isSelectedGrid[idx] = !this.isSelectedGrid[idx] 595 if (this.isSelectedGrid[idx]) { 596 this.numberBadge++; 597 let gridItemName = 'grid' + idx 598 // 选中状态下提前调用componentSnapshot中的get接口获取pixmap 599 this.getUIContext().getComponentSnapshot().get(gridItemName, (error: Error, pixmap: image.PixelMap)=>{ 600 this.pixmap = pixmap 601 this.previewData[idx] = { 602 pixelMap:this.pixmap 603 } 604 }) 605 } else { 606 this.numberBadge--; 607 } 608 }) 609 // 使能多选拖拽,右上角数量角标需要应用设置numberBadge参数 610 .dragPreviewOptions({numberBadge: this.numberBadge},{isMultiSelectionEnabled:true,defaultAnimationBeforeLifting:true}) 611 .onDragStart(()=>{ 612 }) 613 }, (idx: string) => idx) 614 } 615 .columnsTemplate('1fr 1fr 1fr 1fr 1fr') 616 .columnsGap(5) 617 .rowsGap(10) 618 .backgroundColor(0xFAEEE0) 619 }.width('100%').margin({ top: 5 }) 620 } 621} 622``` 623 624 625### 适配自定义落位动效 626 627当开发者需要实现自定义落位动效时,可以禁用系统的默认动效。从API version 18开始,ArkUI提供了[executeDropAnimation](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#executedropanimation18)接口,用于自定义落位动效。以下以Image组件为例,详细介绍使用[executeDropAnimation](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#executedropanimation18)接口的基本步骤,以及开发过程中需要注意的事项。 628 6291. 组件拖拽设置。 630 设置draggable为true,并配置onDragStart,onDragEnd等回调函数。 631 ```ts 632 Image($r('app.media.app_icon')) 633 .width(100) 634 .height(100) 635 .draggable(true) 636 .margin({ left: 15 ,top: 40}) 637 .visibility(this.imgState) 638 .onDragStart((event) => {}) 639 .onDragEnd((event) => {}) 640 ``` 6412. 设置自定义动效。 642 643 自定义落位动效通过[animateTo](../reference/apis-arkui/js-apis-arkui-UIContext.md#animateto)接口设置动画相关的参数来实现。例如,可以改变组件的大小。 644 645 ```ts 646 customDropAnimation = () => { 647 this.getUIContext().animateTo({ duration: 1000, curve: Curve.EaseOut, playMode: PlayMode.Normal }, () => { 648 this.imageWidth = 200; 649 this.imageHeight = 200; 650 this.imgState = Visibility.None; 651 }) 652 } 653 ``` 654 6553. 拖拽落位适配动效。 656 657 设置onDrop回调函数,接收拖拽数据。拖拽落位动效通过[executeDropAnimation](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#executedropanimation18)函数执行,设置[useCustomDropAnimation](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragevent7)为true禁用系统默认动效。 658 659 ```ts 660 Column() { 661 Image(this.targetImage) 662 .width(this.imageWidth) 663 .height(this.imageHeight) 664 } 665 .draggable(true) 666 .margin({ left: 15 }) 667 .border({ color: Color.Black, width: 1 }) 668 .allowDrop([udmfType.UniformDataType.IMAGE]) 669 .onDrop((dragEvent: DragEvent) => { 670 let records: Array<unifiedDataChannel.UnifiedRecord> = dragEvent.getData().getRecords(); 671 let rect: Rectangle = dragEvent.getPreviewRect(); 672 this.imageWidth = Number(rect.width); 673 this.imageHeight = Number(rect.height); 674 this.targetImage = (records[0] as udmf.Image).imageUri; 675 dragEvent.useCustomDropAnimation = true; 676 dragEvent.executeDropAnimation(this.customDropAnimation) 677 }) 678 ``` 679 680**完整示例:** 681 682```ts 683import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData'; 684import { promptAction } from '@kit.ArkUI'; 685 686 687@Entry 688@Component 689struct DropAnimationExample { 690 @State targetImage: string = ''; 691 @State imageWidth: number = 100; 692 @State imageHeight: number = 100; 693 @State imgState: Visibility = Visibility.Visible; 694 695 customDropAnimation = 696 () => { 697 this.getUIContext().animateTo({ duration: 1000, curve: Curve.EaseOut, playMode: PlayMode.Normal }, () => { 698 this.imageWidth = 200; 699 this.imageHeight = 200; 700 this.imgState = Visibility.None; 701 }) 702 } 703 704 build() { 705 Row() { 706 Column() { 707 Image($r('app.media.app_icon')) 708 .width(100) 709 .height(100) 710 .draggable(true) 711 .margin({ left: 15 ,top: 40}) 712 .visibility(this.imgState) 713 .onDragStart((event) => { 714 }) 715 .onDragEnd((event) => { 716 if (event.getResult() === DragResult.DRAG_SUCCESSFUL) { 717 console.log('Drag Success'); 718 } else if (event.getResult() === DragResult.DRAG_FAILED) { 719 console.log('Drag failed'); 720 } 721 }) 722 }.width('45%') 723 .height('100%') 724 Column() { 725 Text('Drag Target Area') 726 .fontSize(20) 727 .width(180) 728 .height(40) 729 .textAlign(TextAlign.Center) 730 .margin(10) 731 .backgroundColor('rgb(240,250,255)') 732 Column() { 733 Image(this.targetImage) 734 .width(this.imageWidth) 735 .height(this.imageHeight) 736 } 737 .draggable(true) 738 .margin({ left: 15 }) 739 .border({ color: Color.Black, width: 1 }) 740 .allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE]) 741 .onDrop((dragEvent: DragEvent) => { 742 let records: Array<unifiedDataChannel.UnifiedRecord> = dragEvent.getData().getRecords(); 743 let rect: Rectangle = dragEvent.getPreviewRect(); 744 this.imageWidth = Number(rect.width); 745 this.imageHeight = Number(rect.height); 746 this.targetImage = (records[0] as unifiedDataChannel.Image).imageUri; 747 dragEvent.useCustomDropAnimation = true; 748 dragEvent.executeDropAnimation(this.customDropAnimation) 749 }) 750 .width(this.imageWidth) 751 .height(this.imageHeight) 752 }.width('45%') 753 .height('100%') 754 .margin({ left: '5%' }) 755 } 756 .height('100%') 757 } 758} 759``` 760 761 762### 处理大批量数据 763 764当多选拖拽的数量较多或者拖拽数据量较大时,在拖拽过程中统一处理数据可能会影响拖拽功能的体验。以下以Grid组件为例,详细介绍在大批量数据拖拽过程中数据的推荐处理方式,以及在开发中需要注意的事项。 765 7661. 组件多选拖拽设置。 767 768 创建GridItem子组件,并设置其状态为可选中。再设置多选拖拽功能isMultiSelectionEnabled为true,最后设置选中状态用作区分是否选中。 769 770 ```ts 771 Grid() { 772 ForEach(this.numbers, (idx: number) => { 773 GridItem() { 774 Column() 775 .backgroundColor(Color.Blue) 776 .width(50) 777 .height(50) 778 .opacity(1.0) 779 .id('grid'+idx) 780 } 781 .dragPreview(this.previewData[idx]) 782 .dragPreviewOptions({numberBadge: this.numberBadge},{isMultiSelectionEnabled:true,defaultAnimationBeforeLifting:true}) 783 .selectable(true) 784 .selected(this.isSelectedGrid[idx]) 785 .stateStyles({ 786 normal : this.normalStyles, 787 selected: this.selectStyles 788 }) 789 .onClick(() => { 790 this.isSelectedGrid[idx] = !this.isSelectedGrid[idx]; 791 }) 792 }, (idx: string) => idx) 793 } 794 ``` 795 796 多选拖拽的数据数量过多可能影响拖拽的体验,推荐多选拖拽最大多选数量为500。 797 798 ```ts 799 onPageShow(): void { 800 let i: number = 0 801 for(i=0;i<500;i++){ 802 this.numbers.push(i) 803 this.isSelectedGrid.push(false) 804 this.previewData.push({}) 805 } 806 } 807 ``` 8082. 多选拖拽选中时添加数据。 809 810 当数据量较大时,建议在选择数据时通过[addRecord](../reference/apis-arkdata/js-apis-data-unifiedDataChannel.md#addrecord)添加数据记录,以避免在拖拽过程中集中添加数据而导致显著的性能消耗。 811 812 ```ts 813 .onClick(()=>{ 814 this.isSelectedGrid[idx] = !this.isSelectedGrid[idx]; 815 if (this.isSelectedGrid[idx]) { 816 let data: UDC.Image = new UDC.Image(); 817 data.uri = '/resource/image.jpeg'; 818 if (!this.unifiedData) { 819 this.unifiedData = new UDC.UnifiedData(data); 820 } 821 this.unifiedData.addRecord(data); 822 this.numberBadge++; 823 let gridItemName = 'grid' + idx; 824 // 选中状态下提前调用componentSnapshot中的get接口获取pixmap 825 this.getUIContext().getComponentSnapshot().get(gridItemName, (error: Error, pixmap: image.PixelMap)=>{ 826 this.pixmap = pixmap; 827 this.previewData[idx] = { 828 pixelMap:this.pixmap 829 } 830 }) 831 } else { 832 this.numberBadge--; 833 for (let i=0; i<this.isSelectedGrid.length; i++) { 834 if (this.isSelectedGrid[i] === true) { 835 this.isSelectedGrid[i] = true; 836 let data: UDC.Image = new UDC.Image(); 837 data.uri = '/resource/image.jpeg'; 838 if (!this.unifiedData) { 839 this.unifiedData = new UDC.UnifiedData(data); 840 } 841 this.unifiedData.addRecord(data); 842 } 843 } 844 } 845 }) 846 ``` 847 8483. 拖拽数据提前准备。 849 850 在onPreDrag中可以提前接收到准备发起拖拽的信号,若数据量较大,此时可以事先准备数据。 851 852 ```ts 853 .onPreDrag((status: PreDragStatus) => { 854 if (status == PreDragStatus.PREPARING_FOR_DRAG_DETECTION) { 855 this.loadData() 856 } 857 }) 858 ``` 859 8604. 数据准备未完成时设置主动阻塞拖拽。 861 862 在发起拖拽时,应判断数据是否已准备完成。若数据未准备完成,则需向系统发出[WAITTING](../reference/apis-arkui/js-apis-arkui-dragController.md#dragstartrequeststatus18)信号。此时,若手指做出移动手势,背板图将停留在原地,直至应用发出READY信号或超出主动阻塞的最大限制时间(5s)。若数据已准备完成,则可直接将数据设置到[dragEvent](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragevent7)中。此外,在使用主动阻塞功能时,需保存当前的dragEvent,并在数据准备完成时进行数据设置;在非主动阻塞场景下,不建议保存当前的dragEvent。 863 864 ```ts 865 .onDragStart((event: DragEvent) => { 866 this.dragEvent = event; 867 if (this.finished == false) { 868 this.getUIContext().getDragController().notifyDragStartRequest(dragController.DragStartRequestStatus.WAITING); 869 } else { 870 event.setData(this.unifiedData); 871 } 872 }) 873 ``` 874 875**完整示例:** 876 877```ts 878import { image } from '@kit.ImageKit'; 879import { unifiedDataChannel as UDC } from '@kit.ArkData'; 880import { dragController } from '@kit.ArkUI'; 881 882@Entry 883@Component 884struct GridEts { 885 @State pixmap: image.PixelMap|undefined = undefined 886 @State numbers: number[] = [] 887 @State isSelectedGrid: boolean[] = [] 888 @State previewData: DragItemInfo[] = [] 889 @State numberBadge: number = 0; 890 unifiedData: UnifiedData|undefined = undefined; 891 timeout: number = 1 892 finished: boolean = false; 893 dragEvent: DragEvent|undefined; 894 895 @Styles 896 normalStyles(): void{ 897 .opacity(1.0) 898 } 899 900 @Styles 901 selectStyles(): void{ 902 .opacity(0.4) 903 } 904 905 onPageShow(): void { 906 let i: number = 0 907 for(i=0;i<500;i++){ 908 this.numbers.push(i) 909 this.isSelectedGrid.push(false) 910 this.previewData.push({}) 911 } 912 } 913 914 loadData() { 915 this.timeout = setTimeout(() => { 916 //数据准备完成后的状态 917 if (this.dragEvent) { 918 this.dragEvent.setData(this.unifiedData); 919 } 920 this.getUIContext().getDragController().notifyDragStartRequest(dragController.DragStartRequestStatus.READY); 921 this.finished = true; 922 }, 4000); 923 } 924 925 @Builder 926 RandomBuilder(idx: number) { 927 Column() 928 .backgroundColor(Color.Blue) 929 .width(50) 930 .height(50) 931 .opacity(1.0) 932 } 933 934 build() { 935 Column({ space: 5 }) { 936 Button('全选') 937 .onClick(() => { 938 for (let i=0;i<this.isSelectedGrid.length;i++) { 939 if (this.isSelectedGrid[i] === false) { 940 this.numberBadge++; 941 this.isSelectedGrid[i] = true; 942 let data: UDC.Image = new UDC.Image(); 943 data.uri = '/resource/image.jpeg'; 944 if (!this.unifiedData) { 945 this.unifiedData = new UDC.UnifiedData(data); 946 } 947 this.unifiedData.addRecord(data); 948 let gridItemName = 'grid' + i; 949 // 选中状态下提前调用componentSnapshot中的get接口获取pixmap 950 this.getUIContext().getComponentSnapshot().get(gridItemName, (error: Error, pixmap: image.PixelMap)=>{ 951 this.pixmap = pixmap 952 this.previewData[i] = { 953 pixelMap:this.pixmap 954 } 955 }) 956 } 957 } 958 }) 959 Grid() { 960 ForEach(this.numbers, (idx: number) => { 961 GridItem() { 962 Column() 963 .backgroundColor(Color.Blue) 964 .width(50) 965 .height(50) 966 .opacity(1.0) 967 .id('grid'+idx) 968 } 969 .dragPreview(this.previewData[idx]) 970 .selectable(true) 971 .selected(this.isSelectedGrid[idx]) 972 // 设置多选显示效果 973 .stateStyles({ 974 normal : this.normalStyles, 975 selected: this.selectStyles 976 }) 977 .onClick(()=>{ 978 this.isSelectedGrid[idx] = !this.isSelectedGrid[idx]; 979 if (this.isSelectedGrid[idx]) { 980 let data: UDC.Image = new UDC.Image(); 981 data.uri = '/resource/image.jpeg'; 982 if (!this.unifiedData) { 983 this.unifiedData = new UDC.UnifiedData(data); 984 } 985 this.unifiedData.addRecord(data); 986 this.numberBadge++; 987 let gridItemName = 'grid' + idx; 988 // 选中状态下提前调用componentSnapshot中的get接口获取pixmap 989 this.getUIContext().getComponentSnapshot().get(gridItemName, (error: Error, pixmap: image.PixelMap)=>{ 990 this.pixmap = pixmap; 991 this.previewData[idx] = { 992 pixelMap:this.pixmap 993 } 994 }) 995 } else { 996 this.numberBadge--; 997 for (let i=0; i<this.isSelectedGrid.length; i++) { 998 if (this.isSelectedGrid[i] === true) { 999 this.isSelectedGrid[i] = true; 1000 let data: UDC.Image = new UDC.Image(); 1001 data.uri = '/resource/image.jpeg'; 1002 if (!this.unifiedData) { 1003 this.unifiedData = new UDC.UnifiedData(data); 1004 } 1005 this.unifiedData.addRecord(data); 1006 } 1007 } 1008 } 1009 }) 1010 .onPreDrag((status: PreDragStatus) => { 1011 // 1.长按时通知,350ms回调 1012 if (status == PreDragStatus.PREPARING_FOR_DRAG_DETECTION) { 1013 // 2.用户按住一段时间,还没有松手,有可能会拖拽,此时可准备数据 1014 this.loadData() 1015 } else if (status == PreDragStatus.ACTION_CANCELED_BEFORE_DRAG) { 1016 // 3.用户停止拖拽交互,取消数据准备(模拟方法:定时器取消) 1017 clearTimeout(this.timeout); 1018 } 1019 }) 1020 // >=500ms,移动超过10vp触发 1021 .onDragStart((event: DragEvent) => { 1022 this.dragEvent = event; 1023 if (this.finished == false) { 1024 this.getUIContext().getDragController().notifyDragStartRequest(dragController.DragStartRequestStatus.WAITING); 1025 } else { 1026 event.setData(this.unifiedData); 1027 } 1028 }) 1029 .onDragEnd(() => { 1030 this.finished = false; 1031 }) 1032 .dragPreviewOptions({numberBadge: this.numberBadge},{isMultiSelectionEnabled:true,defaultAnimationBeforeLifting:true}) 1033 }, (idx: string) => idx) 1034 } 1035 .columnsTemplate('1fr 1fr 1fr 1fr 1fr') 1036 .columnsGap(5) 1037 .rowsGap(10) 1038 .backgroundColor(0xFAEEE0) 1039 }.width('100%').margin({ top: 5 }) 1040 } 1041} 1042``` 1043 1044<!--RP1--><!--RP1End--> 1045