对于拖放功能,大家并不陌生,这是在桌面端最稀松平常的操作,比如将文件拖入回收站。随着移动设备的大屏趋势、可折叠设备的愈加发完善,拖放操作在移动平台里端也显得愈加必要和流行!
针对拖放功能的实现,Android 平台现存的方案略为复杂。基于此, Jetpack 框架集合里推出了新成员 DragAndDrop 。
本文着重阐述该框架的愿景和核心要点,主要内容译自 Android 开发者关系工程师 Paul 在 Meduim 上的 Post:Simplifying drag and drop。
本质来说,拖放手势(drag and drop)指的是用户通过点击选择图片、文本或者其他数据元素,然后直接拖放到 App 的其他界面、甚至其他 App 的界面,接着这个数据就被纳入到新的界面内。这个手势通常表现为在触摸屏上的长按拖动或者非触摸屏上的单击并用鼠标拖动,最后在目标位置`放下。
来看一个 App 内典型的拖放效果:
尽管 Android 一直长期支持拖放功能的实现(比如早在 Android 3.0 即加入的 DragEvent API),但事实证明:想要完整、顺畅地实现针对过程中的手势、触摸事件、权限以及回调的集成,往往比较困难和复杂。
现在我想向大家推荐 Jetpack 的新成员 DragAnd Drop 框架,目前处于 alpha 版本,其旨在辅助你更加简单地处理拖放 App 内的数据。
在 build.gradle 里引入依赖,即可使用。
implementation 'androidx.draganddrop:draganddrop:1.0.0-alpha02'
拖放功能的使用在大屏设备上日益频繁,比如平板电脑和笔记本电脑,尤其是可折叠设备。在这种类型的设备上进行分屏的操作比传统的智能手机多了高达 7 倍。他们的用户常常需要使用分屏或多窗口(https://developer.android.com/guide/topics/large-screens/multi-window-support) 模式来处理多任务的场景,而将数据在不同的 App 间拖放是再自然不过的体验和需求!
Android 平台原生已经支持从输入框控件 EditText 拖动文本,但我们强烈建议开发者实现用户从其他控件拖动数据的功能,支持的数据类型除了文本以外,还能包括图片、文件等任意类型。当然了,反向支持数据从其他 App 拖放进来也同等重要,并值得鼓励。
来看一个 App 之间拖放文本和图片的示例效果:
DragStartHelper
,结合 DropHelper
构成了整个框架最核心的 API,它们可以轻松实现手势支持、数据的回调、样式和像素级的 UI 对齐等实现环节。
DragStartHelper
作为 Jetpack 框架集合 core 包下的工具类, DragStartHelper 负责监测拖动手势的开始时机。这些手势包括长按拖动、单击并用鼠标拖动等。
使用起来很简单,将需要监听的视图包装进来并开始监听。框架会在拖动手势触发的时候回调过来,之后进行一些简单的配置即可。
将需要传递的数据包装到 ClipData 中
新建用于展示拖动效果的图片实例 DragShadowBuilder
将数据和拖动效果外加一些 Flag 交由 View 的原生方法 startDragAndDrop() 进行后续的动作,包括效果的展示和数据的传递等
// Make a view draggable to share a file. DragStartHelper takes care of // intercepting drag gestures and attaching the listener. DragStartHelper(draggableView) { view, _ -> // Sets the appropriate MIME types automatically. val dragClipData = ClipData.newUri(contentResolver, "File", fileUri) // Set the visual look of the dragged object. // Can be extended and customized; we use the default here. val dragShadow = View.DragShadowBuilder(view) // Starts the drag. Note that the global flag allows for cross-app dragging. view.startDragAndDrop( dragClipData, dragShadow, null, // Optional extra local state information // Since this is a "content:" URI and not just plain text, we can use the // DRAG_FLAG_GLOBAL_URI_READ to allow other apps to read from our content // provider. Without it, other apps won't receive the drag events. DRAG_FLAG_GLOBAL or DRAG_FLAG_GLOBAL_URI_READ) ) }.attach()
DropHelper
另一个核心工具类 DropHelper,则关心拖动数据放下的时机和目标视图。
适配的代码简单来讲:
需要针对可拖放数据的试图调用 configureView 方法
其内部还需要设定关心的数据类型即 Mime Type
指定一些其他可选参数实例 DropHelper.Options,比如放下时高亮的颜色和视图范围等
最后设置最重要的放下监听器 OnReceiveContentListener,去从 ClipData 中取得数据执行上传、显示等处理,当然还包括不匹配的警告或视图提醒等
注意:构建 DropHelper.Options 实例的时候,记得调用 addInnerEditTexts(),这样可以确保嵌套的 EditText 控件不会抢夺视图焦点。
DropHelper.configureView( // Activity that will handle the drop this, // Target drop view to be highlighted outerDropTarget, // Supported MIME types arrayOf(MIMETYPE_TEXT_PLAIN, "image/*"), // Options for configuring drop targets DropHelper.Options.Builder() // To ensure proper drop target highlighting, all EditText elements in // the drop target view hierarchy must be included in a call to this // method. Otherwise, an EditText within the target, rather than the // target view itself, acquires focus during the drag and drop operation. .addInnerEditTexts(innerEditText) .build() ) { _, payload -> // Process the payload here, returning any content that should be delegated to // the platform. ... }
了解更多
如上只是精简的介绍,更多细节可以到如下的官方文档进行详细地了解:
拖放功能的官方文档:https://developer.android.com/guide/topics/ui/drag-drop
当然,也可以通过官方的完备 DEMO 进行更深入地学习和实践:
官方 DEMO 地址:https://github.com/android/user-interface-samples/tree/main/DragAndDropAcrossApps
还有一点尤为重要,记得在 Android Issue 网站反馈你遇到的 Bug 或意见:
https://issuetracker.google.com/issues/new?component=1139019
最后,如果你觉得我的阐述存在难以理解的地方,不妨阅读本文翻译的原文:
原文地址:Simplifying drag and drop