1# 拖拽控制 2 3组件提供了一些属性和接口,可用于配置组件对拖拽事件的响应行为,或影响系统对拖拽事件的处理方式,包括是否允许被拖拽,自定义拖拽跟手图的外观等。 4 5> **说明:** 6> 7> 从API Version 10开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 8 9ArkUI框架对以下组件实现了默认的拖拽能力,支持对数据的拖出或拖入响应。开发者也可以通过实现通用拖拽事件来自定义拖拽响应。 10 11- 默认支持拖出能力的组件(可从组件上拖出数据):[Search](ts-basic-components-search.md)、[TextInput](ts-basic-components-textinput.md)、[TextArea](ts-basic-components-textarea.md)、[RichEditor](ts-basic-components-richeditor.md)、[Text](ts-basic-components-text.md)、[Image](ts-basic-components-image.md)、<!--Del-->[FormComponent](ts-basic-components-formcomponent-sys.md)、<!--DelEnd-->[Hyperlink](ts-container-hyperlink.md),开发者可通过设置这些组件的[draggable](ts-universal-attributes-drag-drop.md#draggable)属性来控制对默认拖拽能力的使用。 12 13- 默认支持拖入能力的组件(目标组件可响应拖入数据):[Search](ts-basic-components-search.md)、[TextInput](ts-basic-components-textinput.md)、[TextArea](ts-basic-components-textarea.md)、[RichEditor](ts-basic-components-richeditor.md),开发者可通过设置这些组件的[allowDrop](ts-universal-attributes-drag-drop.md#allowdrop)属性为null来禁用对默认拖入能力的支持。 14 15<!--RP1--><!--RP1End-->其他组件需要开发者将draggable属性设置为true,并在onDragStart等接口中实现数据传输相关内容,才能正确处理拖拽。 16 17> **说明:** 18> 19> Text组件需配合[copyOption](ts-basic-components-text.md#copyoption9)一起使用,设置copyOptions为CopyOptions.InApp或者CopyOptions.LocalDevice。 20 21## allowDrop 22 23allowDrop(value: Array<UniformDataType> | null) 24 25设置该组件上允许落入的数据类型。 26 27**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 28 29**系统能力:** SystemCapability.ArkUI.ArkUI.Full 30 31**参数:** 32 33| 参数名 | 类型 | 必填 | 说明 | 34| ------ | ------------------------------------------------------------ | ---- | ----------------------------------------------- | 35| value | Array\<[UniformDataType](../../apis-arkdata/js-apis-data-uniformTypeDescriptor.md#uniformdatatype)> \| null<sup>12+</sup> | 是 | 设置该组件上允许落入的数据类型。从API version 12开始,允许设置成null使该组件不接受所有的数据类型。<br/>默认值:空 | 36 37## draggable 38 39draggable(value: boolean) 40 41设置该组件是否允许进行拖拽。 42 43**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 44 45**系统能力:** SystemCapability.ArkUI.ArkUI.Full 46 47**参数:** 48 49| 参数名 | 类型 | 必填 | 说明 | 50| ------ | ------- | ---- | ---------------------------------------------- | 51| value | boolean | 是 | 设置该组件是否允许进行拖拽。true表示允许拖拽,false表示不允许拖拽。<br/>默认值:false | 52 53## dragPreview<sup>11+</sup> 54 55dragPreview(value: CustomBuilder | DragItemInfo | string) 56 57设置组件拖拽过程中的预览图。 58 59**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 60 61**系统能力:** SystemCapability.ArkUI.ArkUI.Full 62 63**参数:** 64 65| 参数名 | 类型 | 必填 | 说明 | 66| ------ | ------------------------------------------------------------ | ---- | ------------------------------------------------------------ | 67| value | [CustomBuilder](ts-types.md#custombuilder8) \| [DragItemInfo](ts-universal-events-drag-drop.md#dragiteminfo说明) \| string<sup>12+</sup> | 是 | 设置组件拖拽过程中的预览图,仅在onDragStart拖拽方式中有效。<br/>当组件支持拖拽并同时设置[bindContextMenu](ts-universal-attributes-menu.md#bindcontextmenu8)的预览图时,则长按浮起的预览图以[bindContextMenu](ts-universal-attributes-menu.md#bindcontextmenu8)设置的预览图为准。开发者在[onDragStart](ts-universal-events-drag-drop.md#onDragStart)中返回的背板图优先级低于[dragPreview](ts-universal-attributes-drag-drop.md#dragPreview11)设置的预览图,当设置了[dragPreview](ts-universal-attributes-drag-drop.md#dragPreview11)预览图时,拖拽过程中的背板图使用[dragPreview](ts-universal-attributes-drag-drop.md#dragPreview11)预览图。由于[CustomBuilder](ts-types.md#custombuilder8)需要离线渲染之后才能使用,因此存在一定的性能开销和时延,推荐优先使用 [DragItemInfo](ts-universal-events-drag-drop.md#dragiteminfo说明)中的[PixelMap](../../apis-image-kit/js-apis-image.md#pixelmap7)方式。<br/> 当传入类型为string的id时,则将id对应组件的截图作为预览图。如果id对应的组件无法查找到,或者id对应的组件Visibility属性设置成none/hidden,则对组件自身进行截图作为拖拽预览图。目前截图不含有亮度、阴影、模糊和旋转等视觉效果。<br/>默认值:空<br/> | 68 69## dragPreview<sup>15+</sup> 70 71dragPreview(preview: CustomBuilder | DragItemInfo | string, config?: PreviewConfiguration):T 72 73自定义组件拖拽过程中的预览图,仅用于设置浮起效果或者禁用浮起效果。 74 75**原子化服务API:** 从API version 15开始,该接口支持在原子化服务中使用。 76 77**系统能力:** SystemCapability.ArkUI.ArkUI.Full 78 79**参数:** 80 81| 参数名 | 类型 | 必填 | 说明 | 82| ------ | ------------------------------------------------------------ | ---- | ------------------------------------------------------------ | 83| preview | [CustomBuilder](ts-types.md#custombuilder8) \| [DragItemInfo](ts-universal-events-drag-drop.md#dragiteminfo说明) \| string | 是 | 设置组件拖拽过程中的预览图,仅在onDragStart拖拽方式中有效。<br/>当组件支持拖拽并同时设置[bindContextMenu](ts-universal-attributes-menu.md#bindcontextmenu8)的预览图时,则长按浮起的预览图以[bindContextMenu](ts-universal-attributes-menu.md#bindcontextmenu8)设置的预览图为准。开发者在[onDragStart](ts-universal-events-drag-drop.md#ondragstart)中返回的背板图优先级低于[dragPreview](ts-universal-attributes-drag-drop.md#dragpreview11)设置的预览图,当设置了[dragPreview](ts-universal-attributes-drag-drop.md#dragpreview11)预览图时,拖拽过程中的背板图使用[dragPreview](ts-universal-attributes-drag-drop.md#dragpreview11)预览图。由于[CustomBuilder](ts-types.md#custombuilder8)需要离线渲染之后才能使用,因此存在一定的性能开销和时延,推荐优先使用 [DragItemInfo](ts-universal-events-drag-drop.md#dragiteminfo说明)中的[PixelMap](../../apis-image-kit/js-apis-image.md#pixelmap7)方式。<br/> 当传入类型为string的id时,则将id对应组件的截图作为预览图。如果id对应的组件无法查找到,或者id对应的组件Visibility属性设置成none/hidden,则对组件自身进行截图作为拖拽预览图。目前截图不含有亮度、阴影、模糊和旋转等视觉效果。<br/>默认值:空 | 84| config | [PreviewConfiguration](ts-universal-events-drag-drop.md#previewconfiguration15) | 是 | 对自定义拖拽过程中的预览图进行配置。<br/>只对[dragPreview](#dragpreview11)中的预览生效。<br/>默认值:空 | 85 86## dragPreviewOptions<sup>11+</sup> 87 88dragPreviewOptions(value: DragPreviewOptions, options?: DragInteractionOptions) 89 90设置拖拽过程中背板图处理模式及数量角标的显示。不支持onItemDragStart拖拽方式。 91 92**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 93 94**系统能力:** SystemCapability.ArkUI.ArkUI.Full 95 96**参数:** 97 98| 参数名 | 类型 | 必填 | 说明 | 99| ------ | -------------------------------------------------------------- | ---- | ------------------------------------------------------------ | 100| value | [DragPreviewOptions](#dragpreviewoptions11)<sup>11+</sup> | 是 | 设置拖拽过程中背板图处理模式及数量角标的显示。<br/>默认值:空 | 101| options<sup>12+</sup>| [DragInteractionOptions](#draginteractionoptions12)<sup>12+</sup>| 否 | 设置拖拽过程中背板图浮起的交互模式。<br/>默认值:空| 102 103## DragPreviewOptions<sup>11+</sup> 104 105**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 106 107| 名称 | 类型 | 必填 | 描述 | 108| -------- | -------- | -------- | -------- | 109| mode | [DragPreviewMode](#dragpreviewmode11枚举说明) \| Array<[DragPreviewMode](#dragpreviewmode11枚举说明)><sup>12+</sup> | 否 | 表示拖拽过程中背板图处理模式。<br/>默认值:DragPreviewMode.AUTO<br/>当组件同时设置DragPreviewMode.AUTO和其它枚举值时,以DragPreviewMode.AUTO为准,其它枚举值设置无效。| 110| numberBadge<sup>12+</sup> | boolean \| number | 否 | 控制数量角标是否显示,或强制设置显示的数量。当设置数量角标时取值范围为[0,2<sup>31</sup>-1],超过取值范围时会按默认状态处理。当设置为浮点数时,只显示整数部分。<br/>**说明:** <br>在多选拖拽场景,需通过该接口设置拖拽对象的数量。<br/>默认值:true | 111| modifier<sup>12+</sup> | [ImageModifier](ts-universal-attributes-attribute-modifier.md)| 否 | 用于配置拖拽背板图的样式Modifier对象,可使用图片组件所支持的属性和样式来配置背板图样式(参考示例6),当前支持透明度,阴影,背景模糊度,圆角。文本拖拽只支持默认效果,不支持通过modifier进行自定义。<br/>1.透明度<br/>通过[opacity](ts-universal-attributes-opacity.md#opacity)设置透明度,不透明度的取值范围为0-1。设置0或不设置时采用默认值0.95,设置1或异常值时不透明。<br/>2.阴影<br/>通过[shadow](ts-universal-attributes-image-effect.md#shadow)设置阴影。<br/>3.背景模糊度<br/>通过[backgroundEffect](ts-universal-attributes-background.md#backgroundeffect11)或[backgroundBlurStyle](ts-universal-attributes-background.md#backgroundblurstyle)设置背景模糊度,如果两者同时设置,以backgroundEffect为准。<br/>4.圆角<br/>通过[border](ts-universal-attributes-border.md#border)或[borderRadius](ts-universal-attributes-border.md#borderRadius)设置圆角,当同时在mode和modifier中设置圆角,mode设置的圆角显示优先级低于modifier设置。<br/>默认值:空,无法修改属性| 112| sizeChangeEffect<sup>18+</sup> | [DraggingSizeChangeEffect](#draggingsizechangeeffect18)<sup>18+</sup> | 否 | 用于选择长按浮起图与拖拽跟手图过渡效果。<br/>默认值:DraggingSizeChangeEffect.DEFAULT| 113 114## DragPreviewMode<sup>11+</sup>枚举说明 115 116| 名称 | 枚举值 | 描述 | 117| -------- | ------- | -------- | 118| AUTO | 1 | 系统根据拖拽场景自动改变跟手点位置,根据规则自动对拖拽背板图进行缩放变换等。<br>**原子化服务API**:从API version 12开始,该接口支持在原子化服务中使用。 | 119| DISABLE_SCALE | 2 | 禁用系统对拖拽背板图的缩放行为。<br>**原子化服务API**:从API version 12开始,该接口支持在原子化服务中使用。 | 120| ENABLE_DEFAULT_SHADOW<sup>12+</sup> | 3 | 启用非文本类组件默认阴影效果。<br>**原子化服务API**:从API version 12开始,该接口支持在原子化服务中使用。 | 121| ENABLE_DEFAULT_RADIUS<sup>12+</sup> | 4 | 启用非文本类组件统一圆角效果,默认值12vp。当应用自身设置的圆角值大于默认值或modifier设置的圆角时,则显示应用自定义圆角效果。<br>**原子化服务API**:从API version 12开始,该接口支持在原子化服务中使用。 | 122| ENABLE_DRAG_ITEM_GRAY_EFFECT<sup>18+</sup> | 5 | 启用支持原拖拽对象灰显(透明度)效果,对文本内容拖拽不生效。用户拖起时原对象显示灰显效果,释放时原对象恢复原有效果。开启默认灰显效果后,不建议在拖拽开始后自行修改透明度,如果开发者在拖拽发起后自行修改应用透明度,则灰显效果将被覆盖,且在结束拖拽时无法正确恢复原始透明度效果。<br>**原子化服务API**:从API version 18开始,该接口支持在原子化服务中使用。 | 123| ENABLE_MULTI_TILE_EFFECT<sup>18+</sup> | 6 | 启用支持多选对象鼠标拖拽不聚拢效果,当满足多选的情况下isMultiSelectionEnabled为true且生效时该参数才生效。不聚拢效果优先级高于[dragPreview](#dragpreview11)。不支持二次拖拽、圆角和缩放设置。<br>**原子化服务API**:从API version 18开始,该接口支持在原子化服务中使用。 | 124| ENABLE_TOUCH_POINT_CALCULATION_BASED_ON_FINAL_PREVIEW<sup>18+</sup> | 7 | 启用支持以拖拽预览图初始尺寸计算跟手点位置,鼠标拖拽,设置DragPreviewMode.ENABLE_MULTI_TILE_EFFECT时不生效。<br>**原子化服务API**:从API version 18开始,该接口支持在原子化服务中使用。 | 125 126## DraggingSizeChangeEffect<sup>18+</sup> 127 128当一个节点上同时设置长按浮起预览(参考[bindContextMenu](ts-universal-attributes-menu.md#bindcontextmenu12))与拖拽时,使用该字段设置长按浮起预览图与拖拽跟手图过渡动效方式。 129 130**原子化服务API:** 从API version 18开始,该接口支持在原子化服务中使用。 131 132**系统能力:** SystemCapability.ArkUI.ArkUI.Full 133 134| 名称 | 值 | 说明 | 135| -------- | ------- | -------- | 136| DEFAULT | 0 | 发起拖拽时直接从菜单预览图切换为最终尺寸的拖拽跟手图。 | 137| SIZE_TRANSITION | 1 | 发起拖拽时,由菜单预览图直接切换为拖拽跟手图,但尺寸逐步从菜单预览图尺寸过渡到最终跟手图尺寸,设置DragPreviewMode.DISABLE_SCALE时尺寸过渡不生效。这在长按浮起预览图与拖拽跟手图相同时使用。 | 138| SIZE_CONTENT_TRANSITION | 2 | 发起拖拽时,由菜单预览图逐步过渡切换为最终拖拽跟手图,设置DragPreviewMode.DISABLE_SCALE时尺寸过渡不生效。这常用于菜单预览图与拖拽跟手图差异较大时使用,过渡效果包含内容透明度及尺寸变化。 | 139 140 141## DragInteractionOptions<sup>12+</sup> 142 143| 名称 | 类型 | 必填 | 描述 | 144| -------- | -------- | -------- | -------- | 145| isMultiSelectionEnabled | boolean | 否 | 表示拖拽过程中背板图是否支持多选聚拢效果。true表示支持多选聚拢效果,false表示不支持多选聚拢效果。该参数只在[Grid](ts-container-grid.md)和[List](ts-container-list.md)组件中的[GridItem](ts-container-griditem.md)组件和[ListItem](ts-container-listitem.md)组件生效。<br/>当一个item组件设置为多选拖拽时,该组件的子组件不可拖拽。聚拢组件预览图设置的优先级为[dragPreview](#dragpreview11)中的string,dragPreview中的PixelMap,组件自截图,不支持dragPreview中的Builder形式。<br/>不支持组件绑定[bindContextMenu](ts-universal-attributes-menu.md#bindcontextmenu12)中参数存在isShown的模式。<br/>默认值:false<br/>**原子化服务API**:从API version 12开始,该接口支持在原子化服务中使用。 | 146| defaultAnimationBeforeLifting | boolean | 否 | 表示是否启用长按浮起阶段组件自身的默认点按效果(缩小)。true表示启用默认点按效果,false表示不启用默认点按效果。<br/>默认值:false <br/>**原子化服务API**:从API version 12开始,该接口支持在原子化服务中使用。 | 147| isLiftingDisabled<sup>15+</sup> | boolean | 否 | 表示长按拖拽时,是否禁用浮起效果。true表示禁用浮起效果,false表示不禁用浮起效果。<br/>如果设置为true,当组件支持拖拽并同时设置[bindContextMenu](ts-universal-attributes-menu.md#bindcontextmenu8)时,仅弹出配置的自定义菜单预览。 <br/>默认值:false<br/>**原子化服务API**:从API version 15开始,该接口支持在原子化服务中使用。 | 148| enableEdgeAutoScroll<sup>18+</sup> | boolean | 否 | 设置在拖拽至可滚动组件边缘时是否触发自动滚屏。true表示触发自动滚屏,false表示不触发自动滚屏。<br />默认值:true<br/>**原子化服务API**:从API version 18开始,该接口支持在原子化服务中使用。 | 149| enableHapticFeedback<sup>18+</sup> | boolean | 否 | 表示拖拽时是否启用震动。true表示启用震动,false表示不启用震动。仅在存在蒙层的预览(通过[bindContextMenu](ts-universal-attributes-menu.md#bindcontextmenu12))场景生效。<br/>**注意:** 仅当应用具备 ohos.permission.VIBRATE 权限,且用户启用了触感反馈时才会生效。<br/>默认值:false<br/>**原子化服务API**:从API version 18开始,该接口支持在原子化服务中使用。 | 150 151## 示例 152### 示例1(允许拖拽和落入) 153 154该示例通过配置allowDrop和draggable分别设置组件是否可落入和拖拽。 155 156```ts 157// xxx.ets 158import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData'; 159 160@Entry 161@Component 162struct ImageExample { 163 @State uri: string = "" 164 @State AblockArr: string[] = [] 165 @State BblockArr: string[] = [] 166 @State AVisible: Visibility = Visibility.Visible 167 @State dragSuccess :Boolean = false 168 169 build() { 170 Column() { 171 Text('Image拖拽') 172 .fontSize('30dp') 173 Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceAround }) { 174 Image($r('app.media.icon')) 175 .width(100) 176 .height(100) 177 .border({ width: 1 }) 178 .visibility(this.AVisible) 179 .draggable(true) 180 .onDragEnd((event: DragEvent) => { 181 let ret = event.getResult(); 182 if(ret == 0) { 183 console.log("enter ret == 0") 184 this.AVisible = Visibility.Hidden; 185 } else { 186 console.log("enter ret != 0") 187 this.AVisible = Visibility.Visible; 188 } 189 }) 190 } 191 .margin({ bottom: 20 }) 192 Row() { 193 Column(){ 194 Text('不允许释放区域') 195 .fontSize('15dp') 196 .height('10%') 197 List(){ 198 ForEach(this.AblockArr, (item:string, index) => { 199 ListItem() { 200 Image(item) 201 .width(100) 202 .height(100) 203 .border({width: 1}) 204 } 205 .margin({ left: 30 , top : 30}) 206 }, (item:string) => item) 207 } 208 .height('90%') 209 .width('100%') 210 .allowDrop([uniformTypeDescriptor.UniformDataType.TEXT]) 211 .onDrop((event?: DragEvent, extraParams?: string) => { 212 this.uri = JSON.parse(extraParams as string).extraInfo; 213 this.AblockArr.splice(JSON.parse(extraParams as string).insertIndex, 0, this.uri); 214 console.log("ondrop not udmf data"); 215 }) 216 .border({width: 1}) 217 } 218 .height("50%") 219 .width("45%") 220 .border({ width: 1 }) 221 .margin({ left: 12 }) 222 Column(){ 223 Text('可释放区域') 224 .fontSize('15dp') 225 .height('10%') 226 List(){ 227 ForEach(this.BblockArr, (item:string, index) => { 228 ListItem() { 229 Image(item) 230 .width(100) 231 .height(100) 232 .border({width: 1}) 233 } 234 .margin({ left: 30 , top : 30}) 235 }, (item:string) => item) 236 } 237 .border({width: 1}) 238 .height('90%') 239 .width('100%') 240 .allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE]) 241 .onDrop((event?: DragEvent, extraParams?: string) => { 242 console.log("enter onDrop") 243 let dragData:UnifiedData = (event as DragEvent).getData() as UnifiedData; 244 if(dragData != undefined) { 245 let arr:Array<unifiedDataChannel.UnifiedRecord> = dragData.getRecords(); 246 if(arr.length > 0) { 247 let image = arr[0] as unifiedDataChannel.Image; 248 this.uri = image.imageUri; 249 this.BblockArr.splice(JSON.parse(extraParams as string).insertIndex, 0, this.uri); 250 } else { 251 console.log(`dragData arr is null`) 252 } 253 } else { 254 console.log(`dragData is undefined`) 255 } 256 console.log("ondrop udmf data"); 257 this.dragSuccess = true 258 }) 259 } 260 .height("50%") 261 .width("45%") 262 .border({ width: 1 }) 263 .margin({ left: 12 }) 264 } 265 }.width('100%') 266 } 267} 268``` 269 270 271 272### 示例2(设置预览图) 273 274该示例通过配置dragPreview设置拖拽过程的预览图。 275 276```ts 277// xxx.ets 278@Entry 279@Component 280struct DragPreviewDemo{ 281 @Builder dragPreviewBuilder() { 282 Column() { 283 Text("dragPreview") 284 .width(150) 285 .height(50) 286 .fontSize(20) 287 .borderRadius(10) 288 .textAlign(TextAlign.Center) 289 .fontColor(Color.Black) 290 .backgroundColor(Color.Pink) 291 } 292 } 293 294 @Builder MenuBuilder() { 295 Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { 296 Text("menu item 1") 297 .fontSize(15) 298 .width(100) 299 .height(40) 300 .textAlign(TextAlign.Center) 301 .fontColor(Color.Black) 302 .backgroundColor(Color.Pink) 303 Divider() 304 .height(5) 305 Text("menu item 2") 306 .fontSize(15) 307 .width(100) 308 .height(40) 309 .textAlign(TextAlign.Center) 310 .fontColor(Color.Black) 311 .backgroundColor(Color.Pink) 312 } 313 .width(100) 314 } 315 316 build() { 317 Row() { 318 Column() { 319 Image('/resource/image.jpeg') 320 .width("30%") 321 .draggable(true) 322 .bindContextMenu(this.MenuBuilder, ResponseType.LongPress) 323 .onDragStart(() => { 324 console.log("Image onDragStart") 325 }) 326 .dragPreview(this.dragPreviewBuilder) 327 } 328 .width("100%") 329 } 330 .height("100%") 331 } 332} 333``` 334 335 336 337### 示例3(设置背板图样式) 338 339该示例通过配置dragPreviewOptions为ENABLE_DEFAULT_SHADOW、ENABLE_DEFAULT_RADIUS和ENABLE_DRAG_ITEM_GRAY_EFFECT设置默认阴影、统一圆角效果与灰显效果。 340 341```ts 342// xxx.ets 343@Entry 344@Component 345struct dragPreviewOptionsDemo{ 346 build() { 347 Row() { 348 Column() { 349 Image('/resource/image.jpeg') 350 .margin({ top: 10 }) 351 .width("30%") 352 .draggable(true) 353 .dragPreviewOptions({ mode: DragPreviewMode.AUTO }) 354 Image('/resource/image.jpeg') 355 .margin({ top: 10 }) 356 .width("30%") 357 .border({ radius: { topLeft: 1, topRight: 2, bottomLeft: 4, bottomRight: 8 } }) 358 .draggable(true) 359 .onDragStart(() => { 360 console.log("Image onDragStart") 361 }) 362 .dragPreviewOptions({ mode: [ DragPreviewMode.ENABLE_DEFAULT_SHADOW, DragPreviewMode.ENABLE_DEFAULT_RADIUS, DragPreviewMode.ENABLE_DRAG_ITEM_GRAY_EFFECT ] }) 363 } 364 .width("100%") 365 .height("100%") 366 } 367 } 368} 369``` 370 371 372 373 374### 示例4(设置多选拖拽) 375 376该示例通过配置isMultiSelectionEnabled实现Grid组件的多选拖拽效果。 377 378```ts 379@Entry 380@Component 381struct Example { 382 @State numbers: number[] = [0, 1, 2, 3, 4 , 5, 6, 7, 8] 383 build() { 384 Column({ space: 5}) { 385 Grid() { 386 ForEach(this.numbers, (item: number) => { 387 GridItem() { 388 Column() 389 .backgroundColor(Color.Blue) 390 .width('100%') 391 .height('100%') 392 } 393 .width(90) 394 .height(90) 395 .selectable(true) 396 .selected(true) 397 .dragPreviewOptions({}, {isMultiSelectionEnabled:true}) 398 .onDragStart(()=>{ 399 400 }) 401 }, (item: string) => item) 402 } 403 .columnsTemplate('1fr 1fr 1fr') 404 .rowsTemplate('1fr 1fr 1fr') 405 .height(300) 406 } 407 .width('100%') 408 } 409} 410``` 411 412 413 414### 示例5(设置默认点按效果) 415 416该示例通过配置defaultAnimationBeforeLifting实现Grid组件的默认点按效果。 417 418```ts 419@Entry 420@Component 421struct Example { 422 @State numbers: number[] = [0, 1, 2, 3, 4 , 5, 6, 7, 8] 423 build() { 424 Column({ space: 5}) { 425 Grid() { 426 ForEach(this.numbers, (item: number) => { 427 GridItem() { 428 Column() 429 .backgroundColor(Color.Blue) 430 .width('100%') 431 .height('100%') 432 } 433 .width(90) 434 .height(90) 435 .selectable(true) 436 .selected(true) 437 .dragPreviewOptions({}, {isMultiSelectionEnabled:true, defaultAnimationBeforeLifting:true}) 438 .onDragStart(()=>{ 439 440 }) 441 }, (item: string) => item) 442 } 443 .columnsTemplate('1fr 1fr 1fr') 444 .rowsTemplate('1fr 1fr 1fr') 445 .height(300) 446 } 447 .width('100%') 448 } 449} 450``` 451 452 453 454### 示例6(自定义背板图样式) 455 456该示例通过配置ImageModifier实现Image组件的自定义背板图样式。 457 458```ts 459// xxx.ets 460import { ImageModifier } from '@kit.ArkUI' 461 462@Entry 463@Component 464struct dragPreviewOptionsDemo{ 465 @State myModifier: ImageAttribute = new ImageModifier().opacity(0.5) 466 @State vis: boolean = true 467 @State changeValue: string = '' 468 @State submitValue: string = '' 469 @State positionInfo: CaretOffset = { index: 0, x: 0, y: 0 } 470 controller: SearchController = new SearchController() 471 @State OpacityIndex: number = 0 472 @State OpacityList:(number | undefined | null)[]=[ 473 0.3,0.5,0.7,1,-50,0,10,undefined,null 474 ] 475 build() { 476 Row() { 477 Column() { 478 Text(this.OpacityList[this.OpacityIndex] + "") 479 Button("Opacity") 480 .onClick(()=> { 481 this.OpacityIndex++ 482 if(this.OpacityIndex > this.OpacityList.length - 1){ 483 this.OpacityIndex = 0 484 } 485 }) 486 Image($r('app.media.image')) 487 .margin({ top: 10 }) 488 .width("100%") 489 .draggable(true) 490 .dragPreviewOptions({modifier: this.myModifier.opacity(this.OpacityList[this.OpacityIndex]) as ImageModifier}) 491 } 492 .width("50%") 493 .height("50%") 494 } 495 } 496} 497``` 498 499 500 501### 示例7(图片拖拽设置) 502 503该示例展示了不同图片(在线图片资源、本地图片资源和PixelMap)在拖拽时组件的设置。 504使用网络图片时,需要申请权限ohos.permission.INTERNET。具体申请方式请参考[声明权限](../../../security/AccessToken/declare-permissions.md)。 505 506```ts 507// xxx.ets 508import { uniformTypeDescriptor, unifiedDataChannel } from '@kit.ArkData'; 509import { image } from '@kit.ImageKit'; 510import { request } from '@kit.BasicServicesKit'; 511import { common } from '@kit.AbilityKit'; 512import { fileIo } from '@kit.CoreFileKit'; 513import { buffer } from '@kit.ArkTS'; 514import { BusinessError } from '@kit.BasicServicesKit'; 515 516@Entry 517@Component 518struct ImageDrag { 519 @State targetImage1: string | PixelMap | null = null; 520 @State targetImage2: string | PixelMap | null = null; 521 @State targetImage3: string | PixelMap | null = null; 522 context: Context|undefined = this.getUIContext().getHostContext(); 523 filesDir = this.context?.filesDir; 524 525 public async createPixelMap(pixelMap: unifiedDataChannel.SystemDefinedPixelMap): Promise<image.PixelMap | null> { 526 let mWidth: number = (pixelMap.details?.width ?? -1) as number; 527 let mHeight: number = (pixelMap.details?.width ?? -1) as number; 528 let mPixelFormat: image.PixelMapFormat = 529 (pixelMap.details?.['pixel-format'] ?? image.PixelMapFormat.UNKNOWN) as image.PixelMapFormat; 530 let mItemPixelMapData: Uint8Array = pixelMap.rawData; 531 const opts: image.InitializationOptions = { 532 editable: false, pixelFormat: mPixelFormat, size: { 533 height: mHeight, 534 width: mWidth 535 } 536 }; 537 const buffer: ArrayBuffer = mItemPixelMapData.buffer.slice(mItemPixelMapData.byteOffset, 538 mItemPixelMapData.byteLength + mItemPixelMapData.byteOffset); 539 try { 540 let pixelMap: image.PixelMap = await image.createPixelMap(buffer, opts); 541 return pixelMap; 542 } catch (err) { 543 console.error('dragtest--> getPixelMap', err); 544 return null; 545 } 546 } 547 548 build() { 549 Column() { 550 Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Center }) { 551 // 在线图片资源拖出 552 Column() { 553 Text('Online Image').fontSize(14) 554 Image('https://www.example.com/xxx.png') // 请填写一个具体的网络图片地址 555 .objectFit(ImageFit.Contain).draggable(true) 556 .onDragStart(() => {}) 557 .width(100).height(100) 558 } 559 .border({ width: 2, color: Color.Gray, radius: 5, style: BorderStyle.Dotted }) 560 .alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center) 561 562 // 本地图片资源拖出 563 Column() { 564 Text('Local Image').fontSize(14) 565 Image($r('app.media.example')) 566 .objectFit(ImageFit.Contain).draggable(true) 567 .onDragStart(() => {}) 568 .width(100).height(100) 569 } 570 .border({ width: 2, color: Color.Gray, radius: 5, style: BorderStyle.Dotted }) 571 .alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center) 572 573 // PixelMap拖出 574 Column() { 575 Text('PixelMap').fontSize(14) 576 Image(this.context?.resourceManager.getDrawableDescriptor($r('app.media.example').id).getPixelMap()) 577 .objectFit(ImageFit.Contain).draggable(true) 578 .onDragStart(() => {}) 579 .width(100).height(100) 580 } 581 .border({ width: 2, color: Color.Gray, radius: 5, style: BorderStyle.Dotted }) 582 .alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center) 583 } 584 585 // 落入数据类型为Image 586 Text('Data type is Image').fontSize(14).margin({ top: 10 }) 587 Column() { 588 Image(this.targetImage1) 589 .objectFit(ImageFit.Contain) 590 .width('70%').height('70%') 591 .allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE]) 592 .onDrop((event: DragEvent, extraParams: string) => { 593 // 通过extraParams获取图片 594 let arr: Record<string, object> = JSON.parse(extraParams) as Record<string, object>; 595 let uri = arr['extraInfo']; 596 if (typeof uri == 'string') { 597 this.targetImage1 = uri; 598 599 try { 600 request.downloadFile(this.context, { 601 url: uri, 602 filePath: this.filesDir + '/example.png' 603 }).then((downloadTask: request.DownloadTask) => { 604 let file = fileIo.openSync(this.filesDir + '/example.png', fileIo.OpenMode.READ_WRITE); 605 let arrayBuffer = new ArrayBuffer(1024); 606 let readLen = fileIo.readSync(file.fd, arrayBuffer); 607 let buf = buffer.from(arrayBuffer, 0, readLen); 608 console.info(`The content of file: ${buf.toString()}`); 609 fileIo.closeSync(file); 610 }) 611 } catch (error) {} 612 } 613 }) 614 } 615 .width('70%').height('25%') 616 .border({ width: 2, color: Color.Gray, radius: 5, style: BorderStyle.Dotted }) 617 .alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center) 618 619 Column() { 620 Image(this.targetImage2) 621 .objectFit(ImageFit.Contain) 622 .width('70%').height('70%') 623 .allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE]) 624 .onDrop((event: DragEvent, extraParams: string) => { 625 // 通过uniformTypeDescriptor获取图片 626 let data: UnifiedData = event.getData(); 627 let records: Array<unifiedDataChannel.UnifiedRecord> = data.getRecords(); 628 if (records[0].getType() ===uniformTypeDescriptor.UniformDataType.IMAGE) { 629 let image: unifiedDataChannel.Image = records[0] as unifiedDataChannel.Image; 630 this.targetImage2 = image.imageUri; 631 } 632 }) 633 } 634 .width('70%').height('25%') 635 .border({ width: 2, color: Color.Gray, radius: 5, style: BorderStyle.Dotted }) 636 .alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center) 637 638 // 落入数据类型为PixelMap 639 Text('Data type is PixelMap').fontSize(14).margin({ top: 10 }) 640 Column() { 641 Image(this.targetImage3) 642 .objectFit(ImageFit.Contain) 643 .width('70%').height('70%') 644 .allowDrop([uniformTypeDescriptor.UniformDataType.OPENHARMONY_PIXEL_MAP]) 645 .onDrop(async (event: DragEvent, extraParams: string) => { 646 // 通过uniformTypeDescriptor获取图片 647 let data: UnifiedData = event.getData(); 648 let records: Array<unifiedDataChannel.UnifiedRecord> = data.getRecords(); 649 if (records[0].getType() ===uniformTypeDescriptor.UniformDataType.OPENHARMONY_PIXEL_MAP) { 650 let record: unifiedDataChannel.SystemDefinedPixelMap = records[0] as unifiedDataChannel.SystemDefinedPixelMap; 651 this.targetImage3 = await this.createPixelMap(record); 652 653 // 落盘到本地 654 const imagePackerApi = image.createImagePacker(); 655 let packOpts : image.PackingOption = { format: "image/jpeg", quality:98 }; 656 const path : string = this.context?.cacheDir + "/pixel_map.jpg"; 657 let file = fileIo.openSync(path, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE); 658 imagePackerApi.packToFile(this.targetImage3, file.fd, packOpts).then(() => { 659 // 直接打包进文件 660 }).catch((error : BusinessError) => { 661 console.error('Failed to pack the image. And the error is: ' + error); 662 }) 663 } 664 }) 665 } 666 .width('70%').height('25%') 667 .border({ width: 2, color: Color.Gray, radius: 5, style: BorderStyle.Dotted }) 668 .alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center) 669 670 }.width('100%').height('100%') 671 } 672} 673``` 674 675 676 677### 示例8(设置图片拖拽震动) 678该示例通过设置enableHapticFeedback实现图片拖拽的震动效果。 679```ts 680// xxx.ets 681@Entry 682@Component 683struct DragPreviewDemo{ 684 @Builder MenuBuilder() { 685 Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { 686 Text("menu item 1") 687 .fontSize(15) 688 .width(100) 689 .height(40) 690 .textAlign(TextAlign.Center) 691 .fontColor(Color.Black) 692 .backgroundColor(Color.Pink) 693 Divider() 694 .height(5) 695 Text("menu item 2") 696 .fontSize(15) 697 .width(100) 698 .height(40) 699 .textAlign(TextAlign.Center) 700 .fontColor(Color.Black) 701 .backgroundColor(Color.Pink) 702 } 703 .width(100) 704 } 705 706 build() { 707 Row() { 708 Column() { 709 Image($r('app.media.app_icon')) 710 .width("30%") 711 .draggable(true) 712 .dragPreviewOptions({}, {isMultiSelectionEnabled:true, defaultAnimationBeforeLifting:true, enableHapticFeedback: true}) 713 .bindContextMenu(this.MenuBuilder, ResponseType.LongPress) 714 .onDragStart(() => { 715 console.log("Image onDragStart") 716 }) 717 } 718 .width("100%") 719 } 720 .height("100%") 721 } 722} 723``` 724 725### 示例9(自定义预览图) 726该示例通过配置onlyForLifting实现自定义预览图,仅用于浮起效果以及配置isLiftingDisabled实现禁用浮起效果。 727```ts 728// xxx.ets 729@Entry 730@Component 731struct LiftingExampleDemo { 732 @Builder 733 dragPreviewBuilder() { 734 Column() { 735 Text("dragPreview builder") 736 .width(150) 737 .height(50) 738 .fontSize(20) 739 .borderRadius(10) 740 .textAlign(TextAlign.Center) 741 .fontColor(Color.Black) 742 .backgroundColor(Color.Green) 743 } 744 } 745 @Builder 746 MenuBuilder() { 747 Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { 748 Text("menu 1") 749 .fontSize(25) 750 .width(200) 751 .height(60) 752 .textAlign(TextAlign.Center) 753 .fontColor(Color.Black) 754 .backgroundColor(Color.Green) 755 Divider() 756 .height(5) 757 Text("menu 2") 758 .fontSize(25) 759 .width(200) 760 .height(60) 761 .textAlign(TextAlign.Center) 762 .fontColor(Color.Black) 763 .backgroundColor(Color.Green) 764 } 765 .width(100) 766 } 767 build() { 768 Column() { 769 Column() { 770 Text("禁用浮起效果") 771 .fontSize(30) 772 .height(30) 773 .backgroundColor('#FFFFFF') 774 .margin({ top: 30 }) 775 Image($r('app.media.startIcon')) 776 .width("40%") 777 .draggable(true) 778 .margin({ top: 15 }) 779 .bindContextMenu(this.MenuBuilder, ResponseType.LongPress) 780 .onDragStart(() => { 781 }) 782 .dragPreviewOptions({}, { 783 isLiftingDisabled: true 784 }) 785 .dragPreview(this.dragPreviewBuilder, { 786 onlyForLifting: true, 787 delayCreating: true 788 }) 789 }.width("%") 790 Column() { 791 Text("仅用于浮起效果") 792 .fontSize(30) 793 .height(30) 794 .backgroundColor('#FFFFFF') 795 .margin({ top: 80 }) 796 Image($r('app.media.startIcon')) 797 .width("40%") 798 .draggable(true) 799 .margin({ top: 15 }) 800 .onDragStart(() => { 801 }) 802 .dragPreviewOptions({}, { 803 isLiftingDisabled: false 804 }) 805 .dragPreview(this.dragPreviewBuilder, { 806 onlyForLifting: true, 807 delayCreating: true 808 }) 809 }.width("100%") 810 }.height("100%") 811 } 812} 813``` 814 815自定义预览图用于浮起效果。 816 817 818 819自定义预览图禁用浮起效果。 820 821 822 823### 示例10(以拖拽预览图初始尺寸计算跟手点位置) 824该示例通过配置DragPreviewMode.ENABLE_TOUCH_POINT_CALCULATION_BASED_ON_FINAL_PREVIEW实现以拖拽预览图初始尺寸计算跟手点位置,设置DragPreviewMode.ENABLE_MULTI_TILE_EFFECT时不生效。 825```ts 826@Entry 827@Component 828struct Index { 829 private iconStr: ResourceStr = $r("app.media.app_icon") 830 831 @Builder 832 MyPreview() { 833 Image($r('app.media.image')) 834 .width(100) 835 .height(100) 836 } 837 838 @Builder 839 MyMenuPreview() { 840 Column() { 841 Image($r('app.media.image')) 842 .width(100) 843 .height(100) 844 } 845 .backgroundColor(Color.Green) 846 .width(300) 847 .height(300) 848 } 849 850 @Builder 851 MyMenu() { 852 Menu() { 853 MenuItem({ startIcon: this.iconStr, content: "菜单选项" }) 854 MenuItem({ startIcon: this.iconStr, content: "菜单选项" }) 855 } 856 } 857 858 @Builder 859 SubMenu() { 860 Menu() { 861 MenuItem({ content: "复制", labelInfo: "Ctrl+C" }) 862 MenuItem({ content: "粘贴", labelInfo: "Ctrl+V" }) 863 } 864 } 865 866 build() { 867 NavDestination() { 868 Scroll() { 869 Column() { 870 Text("no ENABLE_TOUCH_POINT_CALCULATION_BASED_ON_FINAL_PREVIEW") 871 Image($r('app.media.image')) 872 .width(200) 873 .height(200) 874 .bindContextMenu(this.MyMenu, ResponseType.LongPress, { 875 preview: this.MyPreview 876 }) 877 .dragPreview(this.MyMenuPreview) 878 .draggable(true) 879 880 Text("ENABLE_TOUCH_POINT_CALCULATION_BASED_ON_FINAL_PREVIEW") 881 Image($r('app.media.image')) 882 .width(200) 883 .height(200) 884 .bindContextMenu(this.MyMenu, ResponseType.LongPress, { 885 preview: this.MyPreview 886 }) 887 .dragPreview(this.MyMenuPreview) 888 .draggable(true) 889 .dragPreviewOptions({ 890 mode: [DragPreviewMode.ENABLE_TOUCH_POINT_CALCULATION_BASED_ON_FINAL_PREVIEW] 891 }) 892 }.width('100%') 893 } 894 } 895 .height('100%') 896 .width('100%') 897 } 898} 899``` 900 901 902 903### 示例11(长按浮起预览图与拖拽跟手图过渡动效) 904该示例通过配置DraggingSizeChangeEffect实现不同拖拽过渡效果。 905```ts 906@Entry 907@Component 908struct Index { 909 private iconStr: ResourceStr = $r("app.media.app_icon") 910 911 @Builder 912 MyPreview() { 913 Image($r('app.media.image')) 914 .width(200) 915 .height(200) 916 } 917 918 @Builder 919 MyMenuPreviewSame() { 920 Column() { 921 Image($r('app.media.image')) 922 .width(300) 923 .height(300) 924 } 925 } 926 927 @Builder 928 MyMenuPreview() { 929 Column() { 930 Image($r('app.media.startIcon')) 931 .width(300) 932 .height(300) 933 } 934 } 935 936 @Builder 937 MyMenu() { 938 Menu() { 939 MenuItem({ startIcon: this.iconStr, content: "菜单选项" }) 940 MenuItem({ startIcon: this.iconStr, content: "菜单选项" }) 941 } 942 } 943 944 @Builder 945 SubMenu() { 946 Menu() { 947 MenuItem({ content: "复制", labelInfo: "Ctrl+C" }) 948 MenuItem({ content: "粘贴", labelInfo: "Ctrl+V" }) 949 } 950 } 951 952 build() { 953 Column() { 954 Text("sizeChangeEffect: SIZE_TRANSITION,长按弹出菜单,拖拽移动后菜单预览图过渡到跟手图,有缩放无叠加效果") 955 .margin({ top: 10 }) 956 Image($r('app.media.image')) 957 .width(200) 958 .height(200) 959 .bindContextMenu(this.MyMenu, ResponseType.LongPress, { 960 preview: this.MyMenuPreviewSame 961 }) 962 .dragPreview(this.MyPreview) 963 .dragPreviewOptions({ 964 sizeChangeEffect: DraggingSizeChangeEffect.SIZE_TRANSITION 965 }) 966 .draggable(true) 967 968 Text("sizeChangeEffect: SIZE_CONTENT_TRANSITION,长按弹出菜单,拖拽移动后菜单预览图和拖拽跟手图两层叠加过渡") 969 .margin({ top: 10 }) 970 Image($r('app.media.image')) 971 .width(200) 972 .height(200) 973 .bindContextMenu(this.MyMenu, ResponseType.LongPress, { 974 preview: this.MyMenuPreview 975 }) 976 .dragPreview(this.MyPreview) 977 .dragPreviewOptions({ 978 sizeChangeEffect: DraggingSizeChangeEffect.SIZE_CONTENT_TRANSITION 979 }) 980 .draggable(true) 981 } 982 .height('100%') 983 .width('100%') 984 } 985} 986``` 987 988