Drag-and-Drop — 实质,移动应用中的 Drag and Drop

作者: IT Sectr 发布日期: 2026-02-27 阅读时间: 9 分钟

Drag-and-Drop — 是一种互动式手势,用手指在屏幕上拖动视觉对象。该手势由三个阶段组成:捕获(长按或触摸)、移动(手指带着对象移动)和释放(抛到目标区域)。用于改变元素顺序、在文件夹之间移动文件、组织界面(拖动重排、拖动调整大小)。在 iOS 中通过 UIDragInteraction 实现,在 Android 中通过 DragEvent 和 startDragAndDrop 实现。

要点

  • 三个阶段 — Drag-and-Drop 包括捕获、移动和释放对象
  • UIDragInteraction — iOS 中用于发起和处理拖拽的主要类
  • startDragAndDrop — 捕获后开始拖拽的 Android View 方法
  • Drag shadow — 被拖拽对象的视觉阴影,跟随手指
  • Drop target — 抛放区域,当被拖拽对象移到上方时亮起

什么是 Drag-and-Drop?

Drag-and-Drop(DnD,拖动)— 是一种操控图形界面元素的方法,用户用手指或鼠标捕获对象,将其移动到另一位置并释放。在移动界面中,Drag-and-Drop 通常通过长按(Long Press)发起 — 用户按住元素 300–500 毫秒,然后系统将界面切换到拖拽模式,手指的移动会带动对象。

历史上,Drag-and-Drop 出现在桌面界面中(Xerox Star,1981;Macintosh,1984),作为复制/粘贴命令的替代方案。在移动平台上,完整支持出现在 iOS 11(2017)的 UIDragInteraction 和 Android 7.0(2016)的 Multi-Window Drag-and-Drop。此前,移动应用中的拖拽是通过自定义解决方案使用 gesture recognizers 实现的。

关键概念:drag session(拖拽会话)、drag items(被拖拽对象)、drag preview(对象的视觉图像)、drop target(抛放区域)、drop proposal(抛放时的动作:移动、复制、链接)。系统在操作系统层面管理会话,这使得对象可以在不同应用之间拖拽(例如,照片从相册拖到备忘录)。

Drag-and-Drop 的阶段

Drag-and-Drop 分为三个连续阶段。阶段 1 — 捕获(Drag Start):用户发起手势(通常为 Long Press),系统创建拖拽会话,捕获对象数据并显示 drag preview — 一个半透明的元素副本,跟随手指。在此阶段,系统会拒绝其他手势(滚动、选择文本)直到 DnD 完成。

阶段 2 — 移动(Drag Move):手指在屏幕上移动,drag preview 跟随触摸点。系统不断确定手指下方的 drop target,并发送 drag enter、drag move 和 drag exit 事件。Drop target 可以视觉反应:亮起、扩展或显示插入线。这个阶段持续 0.5 到 10+ 秒,具体取决于任务的复杂程度。

阶段 3 — 释放(Drop):手指离开屏幕。系统结束拖拽会话并在目标元素处调用 drop callback。如果 drop target 拒绝抛放(例如,将文件拖到没有写入权限的文件夹),drag preview 会以动画方式返回原始位置(取消动画)。如果抛放被接受,元素从原始位置消失并出现在目标位置。

阶段iOS 事件Android 事件视觉状态
捕获UIDragInteractionDelegate.dragSessionWillBeginView.OnLongClickListener → startDragAndDropDrag preview 出现在手指下方
移动UIDropInteractionDelegate.dragSessionDidUpdateView.OnDragListener.onDrag (ACTION_DRAG_MOVED)Preview 移动,target 亮起
释放UIDropInteractionDelegate.performDropView.OnDragListener.onDrag (ACTION_DROP)抛放或返回动画

应用间拖拽 — 两个平台均可用的功能。iOS 支持在 Split View 和 Slide Over 模式下(iPad)在应用之间进行 Drag-and-Drop。Android 支持在 Split Screen 模式下在应用之间拖拽。数据通过 UIPasteConfiguration(iOS)或 ClipData(Android)传输。开发者指定支持的数据类型(iOS 的 UTI,Android 的 MIME)。

iOS 中的 Drag-and-Drop:UIDragInteraction

UIDragInteraction — iOS 的主要拖拽 API,在 iOS 11 中添加。要从 View 启用拖拽,请添加 UIDragInteraction 和 UIDragInteractionDelegate。委托人确定哪些元素被拖拽、它们的外观和行为。对于接收元素,使用带有 UIDropInteractionDelegate 的 UIDropInteraction,该委托人确定 View 是否可以接受抛放以及如何处理数据。

swift
// UIDragInteraction — 从集合中拖拽元素
class DragViewController: UIViewController, UIDragInteractionDelegate {

    @IBOutlet var dragSourceView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        let dragInteraction = UIDragInteraction(delegate: self)
        dragSourceView.addInteraction(dragInteraction)
    }

    func dragInteraction(_ interaction: UIDragInteraction,
                          itemsForBeginning session: UIDragSession) -> [UIDragItem] {

        guard let data = Data(contentsOf: selectedFileURL) else { return [] }

        let itemProvider = NSItemProvider(object: data as NSData)
        let dragItem = UIDragItem(itemProvider: itemProvider)

        // Drag preview — 元素的缩小副本
        dragItem.previewProvider = {
            let previewView = UIImageView(image: thumbnailImage)
            previewView.alpha = 0.8
            return UIDragPreview(view: previewView)
        }
        return [dragItem]
    }
}

UIDropInteractionDelegate — 用于接收抛放的镜像接口。canHandle(_:) 方法检查数据类型是否被支持。sessionDidUpdate(_:) 视觉更新状态(亮起、插入线)。performDrop(_:) 执行数据的最终处理。iOS 会自动调用带有操作类型的 UIDropProposal:.move(移动)、.copy(复制)、.forbidden(禁止)。操作类型影响视觉显示(复制时在 preview 角落显示 + 图标,forbidden 显示禁止符号)。

Android 中的 Drag-and-Drop:startDragAndDrop

View.startDragAndDrop() — Android SDK 用于开始拖拽的方法,从 API 24(Android 7.0)开始可用。在捕获元素后调用(通常在 onLongClick 中)。参数:ClipData(数据)、DragShadowBuilder(视觉阴影)、Object myLocalState(本地状态)、flags。DragShadowBuilder 创建一个跟随手指的 Bitmap。系统在拖拽过程中自动管理动画和触摸事件。

kotlin
// 使用重新排序拖拽 RecyclerView 元素
class ReorderAdapter(private val items: MutableList<String>) :
    RecyclerView.Adapter<ReorderAdapter.ViewHolder>() {

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.itemView.setOnLongClickListener {
            // 创建用于数据传输的 ClipData
            val clipData = ClipData.newPlainText("item", items[position])

            // 带有缩小 preview 的自定义 DragShadowBuilder
            val shadowBuilder = object : View.DragShadowBuilder(holder.itemView) {
                override fun onProvideShadowMetrics(shadowSize: Point, shadowTouchPoint: Point) {
                    shadowSize.set(itemView.width, itemView.height / 2)
                }
            }

            holder.itemView.startDragAndDrop(clipData, shadowBuilder, position, 0)
            true
        }
    }
}

接受抛放 在 Android 中通过 View.setOnDragListener() 实现。监听器接收 ACTION_DRAG_STARTED、ACTION_DRAG_ENTERED、ACTION_DRAG_LOCATION、ACTION_DRAG_EXITED、ACTION_DROP、ACTION_DRAG_ENDED 事件。在 ACTION_DROP 事件中,数据从 ClipData 提取并执行目标操作。对于需要重新排序的 RecyclerView,建议使用 ItemTouchHelper.SimpleCallback — 这是一个内置机制,自动处理用于捕获的 Long Press、元素移动动画以及 onMove/onSwiped 辅助方法。

Drag-and-Drop 的应用场景

重新排序列表(drag to reorder)— 最常见的 DnD 场景:用户拖拽列表元素以改变它们的顺序。示例:重新排列收藏联系人、在任务跟踪器(Trello、Todoist)中排序任务、改变播放列表中歌曲的顺序(Spotify、Apple Music)。通过 Android 中的 ItemTouchHelper 或 iOS 中的 UILongPressGestureRecognizer 加单元格重排实现。

文件管理器 — 用于在文件夹之间复制和移动文件的 Drag-and-Drop。iOS Files App、Android Files by Google、Dropbox — 所有都支持 DnD。将文件拖到文件夹上会将其移动到内部(move),拖动时按住 Option/Ctrl 键会复制(copy)。Drop target 在靠近时视觉亮起,显示文件夹边界。

看板 — Trello、Jira、Notion 使用 Drag-and-Drop 将卡片在列之间移动(To Do → In Progress → Done)。拖拽时,卡片从源列脱离,跟随手指,并在越过另一列的边界时以动画方式插入。iOS 11+ 支持带有视差效果的 UIDragPreview,其中 preview 稍微偏离触摸点,创造深度感。

Springboard 模块(iOS) — 在 iOS 主屏幕上拖拽应用图标。长按图标会激活“jiggling”模式(图标振动),然后用户可以在页面之间移动图标并创建文件夹。这是最复杂的 DnD UX 模式之一,因为它需要同时处理拖拽、振动动画和归类到文件夹。

Drag-and-Drop 的 UX 建议

视觉反馈 — 对 Drag-and-Drop 至关重要。被拖拽的对象应该变化:缩小(iOS:scale 0.8–0.9)、变成半透明(alpha 0.7–0.8)并带着阴影升到界面上方(shadow offset)。Drop target 应该对靠近做出视觉反应:亮起(highlight)、扩展(expand)或显示边框(border)。没有这些信号,用户无法理解可以将对象抛到哪里。

返回动画(取消拖拽)— 如果用户在抛放区域外或无效区域上方释放元素,对象应以动画方式返回原始位置。iOS UIDragInteractionDelegate 提供 dragInteraction(_:sessionDidMove:) 用于跟踪和 dragInteraction(_:session:willEndWith:) 用于返回动画。Android View.setOnDragListener 接收 ACTION_DRAG_ENDED,在其中可以通过 ValueAnimator 或 ObjectAnimator 启动返回动画。

建议iOSAndroid
手势捕获Long Press(默认)OnLongClickListener
Drag previewUIDragItem.previewProviderDragShadowBuilder
触觉反馈UIImpactFeedbackGeneratorHapticFeedbackConstants.LONG_PRESS
返回动画UIDragPreviewTargetViewPropertyAnimator
Drop proposalUIDropProposal (.move/.copy)ACTION_DROP + 标志
应用间 DnDiOS 11+ Split View (iPad)Android 7.0+ Split Screen

无障碍:有运动限制的用户可能无法执行拖拽手势。通过列表的“向上/向下移动”按钮、文件的“移到文件夹”上下文菜单提供替代方案。iOS VoiceOver 支持用于移动元素的“Drag”旋转器操作。Android TalkBack 通过 AccessibilityNodeInfo.addAction() 提供自定义操作。Nike Run Club 和 Apple Health 使用 DnD 重新排序仪表盘上的指标 — 通过标准按钮访问。

常见问题

什么手势用于发起 Drag-and-Drop?

发起 Drag-and-Drop 的标准手势是 Long Press(长按),持续 300–500 毫秒。iOS 使用内置的 UIDragInteraction 机制,自动将长时间触摸转换为捕获。Android 需要在 OnLongClickListener 回调中显式调用 startDragAndDrop()。在某些情况下(例如在游戏中),DnD 可以通过简单触摸开始而无需延迟。

可以在不同应用之间拖拽对象吗?

可以,应用间 Drag-and-Drop 在 iOS 11+(iPad)和 Android 7.0+ 上受支持。在 iPad 上,拖拽在 Split View 和 Slide Over 模式下工作 — 用户可以从 Safari 获取文本并将其移到备忘录。在 Android 中,应用间 DnD 在 Split Screen 中工作。数据通过 UIPasteConfiguration(iOS)或 ClipData(Android)传输,并指定支持的类型(UTI、MIME)。

如何避免 Drag-and-Drop 与滚动的冲突?

DnD 和 Scroll 的冲突通过捕获延迟解决。如果用户在 Long Press 超时(500 毫秒)之前开始移动手指,该手势将被解释为滚动,而非 Drag-and-Drop。在 iOS 中,UIDragInteraction 通过 cancelsTouchesInView 自动管理此冲突。在 Android 中,ItemTouchHelper 使用阈值 — 如果移动超过 TouchSlop 阈值,Long Press 将被取消以进行滚动。

拖拽时可以传输哪些数据?

Drag-and-Drop 支持所有类型的数据:文本、图像、文件、URL、自定义对象。iOS 使用带有 UTI 类型的 NSItemProvider(public.text、public.image、public.file-url)。Android 使用带有 MIME 类型的 ClipData(text/plain、image/png、application/octet-stream)。对于自定义数据,创建带有 Intent 的 ClipData.Item。在应用间 DnD 中,数据被封装为通用格式:plein text、HTML、RTF、UIImage/PNG。

如何在取消 Drag-and-Drop 时动画元素返回?

在 iOS 中,返回动画通过 UIDragPreviewTarget 管理 — preview 以动画方式返回的最终位置。在 Android 中,使用带有 translateX/translateY 的 ViewPropertyAnimator 将元素返回原始位置。动画时长为 200–300 毫秒,采用 easing 曲线(EaseOut)。UIKit 自动添加带有视差效果的 UIDragPreview,在取消手势时平滑返回。

总结

  • Drag-and-Drop — 拖拽对象的方法,包括三个阶段:捕获、移动、释放
  • 发起 — 通常通过 Long Press(300–500 毫秒)以区分于滚动
  • iOS API — UIDragInteraction(源)+ UIDropInteraction(接收器)
  • Android API — View.startDragAndDrop()(源)+ View.setOnDragListener(接收器)
  • ItemTouchHelper — 用于 RecyclerView 重新排序的现成 Android 解决方案
  • Drag preview — 对象的视觉阴影,以半透明方式跟随手指
  • 返回动画 — 在无效区域外抛放时必须使用以提供清晰的 UX

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读