• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![zh-cn_image_0000001562820825](figures/zh-cn_image_0000001562820825.png)
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![pixelMap](figures/pixelMap.png)
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.None208        // 显式设置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![commonDrag](figures/commonDrag.gif)
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![multiDrag](figures/multiDrag.gif)
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![executeDropAnimation](figures/executeDropAnimation.gif)
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![patchDataProcess](figures/patchDataProcess.gif)
1044<!--RP1--><!--RP1End-->
1045