小米手机设备全局拖拽功能技术适配说明
1.简介
安卓拖拽分享功能提供了一种跨窗口传递数据的功能,文本、图像或任何可以用uri表示的数据都可以通过拖拽从一个窗口传递到另一个窗口。
可参考谷歌官方文档:Drag and drop | Android Developers
app适配拖拽功能主要分为拖出适配和拖入适配,本文将分别简介其适配方法。
2.拖出适配
app对任意view调用startDragAndDrop方法即可实现拖出。本章分别对拖出文字、拖出图片、拖出任意文件进行演示。
2.1.拖出文字
使用一个TextView来拖出文字:
// 拖出文字示例
findViewById<TextView>(R.id.drag_text_view).setOnLongClickListener { view -> // 设置长按回调
val textView = view as TextView
val clipData = ClipData.newPlainText("label", textView.text) // 构建存放文本的ClipData
// 调用view.startDragAndDrop方法开始拖拽
textView.startDragAndDrop(clipData, // 传入clipData
View.DragShadowBuilder(textView), // 使用textView的draw方法绘制拖拽的图像
null, // 传入一个本地数据对象
View.DRAG_FLAG_GLOBAL or View.DRAG_FLAG_GLOBAL_URI_READ) // 加入这些flag允许跨窗口拖拽
true
}
我们只需要使用ClipData.newPlainText构建一个保存文本的clipData,再调用view.startDragAndDrop方法,将这个clipData作为参数传入,即可实现文字的拖出。
安卓的EditText本身就实现了文字的拖出和拖入,不需要额外适配。
2.2.拖出图片
使用一个ImageView来拖出图片:
// 拖出图片示例
findViewById<ImageView>(R.id.drag_image_view).setOnLongClickListener { imageView -> // 设置长按回调
val imageUri = getFileUri(R.mipmap.drag_image, "drag_image.png") // 通过fileProvider生成图像文件uri
val clipData = ClipData("label", arrayOf("image/png"), ClipData.Item(imageUri)) // 使用imageUri构建ClipData
// 调用view.startDragAndDrop方法开始拖拽
imageView.startDragAndDrop(clipData, // 传入clipData
View.DragShadowBuilder(imageView), // 使用imageView的draw方法绘制拖拽的图像
null, // 传入一个本地数据对象
View.DRAG_FLAG_GLOBAL or View.DRAG_FLAG_GLOBAL_URI_READ) // 加入这些flag允许跨窗口拖拽
true // 返回true表示长按事件被处理了
}
其中getFileUri方法将安卓资源中的一张图片作为文件保存到本地,再使用FileProvider得到图片文件的uri,如果对具体实现有兴趣可以阅读源码。最终我们是将uri放入ClipData中,再调用view.startDragAndDrop方法,将这个clipData作为参数传入,即可实现图片的拖出。
2.3.拖出任意文件
任意文件和图片一样,使用一个uri来表示,因网站一键生成APP此拖出任意文件可以使用和拖出图片类似的方法实现。
使用一个Button来选择任意文件,然后使用一个TextView来显示文件uri并实现文件的拖出:
// 拖出任意文件示例
findViewById<Button>(R.id.choose_file_button).setOnClickListener { chooseFile() } // 选择文件按钮
mDragFileView = findViewById(R.id.drag_file_view)
mDragFileView.setOnLongClickListener { // 设置长按回调
if (mFileUri != null) { // 选择的文件保存在mFileUri,如果其不为null表示已经选择了一个文件
val clipData = ClipData.newRawUri("label", mFileUri) // 使用mFileUri构建ClipData
// 调用view.startDragAndDrop方法开始拖拽
mDragFileView.startDragAndDrop(
clipData, // 传入clipData
View.DragShadowBuilder(mDragFileView), // 使用imageView的draw方法绘制拖拽的图像
null, // 传入一个本地数据对象
View.DRAG_FLAG_GLOBAL or View.DRAG_FLAG_GLOBAL_URI_READ) // 加入这些flag允许跨窗口拖拽
}
true // 返回true表示长按事件被处理了
}
3.拖入适配
app对任意view注册OnDragListener监听器即可实现拖入处理。本章分别对拖入文字、拖入图片进行演示。
3.1.拖入文字
使用一个TextView来拖入文字:
// 拖入文字示例
findViewById<TextView>(R.id.drop_text_view).setOnDragListener { view, event -> // 设置拖拽监听器
val textView = view as TextView
when (event.action) { // 对拖拽不同的事件进行处理
DragEvent.ACTION_DRAG_STARTED -> {
val hasText = event.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN) // 查找是否有文字类型的数据
if (!hasText) { // 没有文字类型的数据
return@setOnDragListener false // 返回false代表这次拖拽不再继续接收拖拽事件
}
}
DragEvent.ACTION_DROP -> { // ACTION_DROP事件表示拖拽抬手结束的时候
textView.text = event.clipData.getItemAt(0).text // 将文字设到textView上显示
}
}
true // 返回true代表拖拽事件被处理了
}
我们只需要通过view.setOnDragListener方法注册一个监听器,并在监听器里面处理拖拽事件即可实现拖入。其中在ACTION_DRAG_STARTED事件中对数据类型进行判断,如果不是我们想要的数据类型就返回false即可不再接收本次拖拽事件;最后在ACTION_DROP事件中获取ClipData数据并进行相应的处理。
3.2.拖入图片
使用一个ImageView来拖入文字:
// 拖入图片示例
findViewById<ImageView>(R.id.drop_image_view).setOnDragListener { view, event -> // 设置拖拽监听器
val imageView = view as ImageView
when (event.action) { // 对拖拽不同的事件进行处理
DragEvent.ACTION_DRAG_STARTED -> {
val mimeTypes = event.clipDescription.filterMimeTypes("image/*") // 查找是否有图像类型的数据
if (mimeTypes == null) { // 没有图像类型的数据
return@setOnDragListener false // 返回false代表这次拖拽不再继续接收拖拽事件
}
}
DragEvent.ACTION_DROP -> { // ACTION_DROP事件表示拖拽抬手结束的时候
requestDragAndDropPermissions(event) // 申请读取uri的权限
imageView.setImageURI(event.clipData.getItemAt(0).uri) // 将图像uri设到imageView上显示
}
}
true // 返回true代表拖拽事件被处理了
}
与拖入文字不同的地方在于,图片是一个用uri表示的文件,要访问这个uri之前必须调用Activity.requestDragAndDropPermissions方法申请权限。
3.3.拖入任意文件
类似于拖入图片,想要拖入任意文件只需要对拖过来的任意uri进行处理即可,示例代码如下:
// 拖入任意文件示例
view.setOnDragListener { view, event -> // 设置拖拽监听器
when (event.action) { // 对拖拽不同的事件进行处理
DragEvent.ACTION_DROP -> { // ACTION_DROP事件表示拖拽抬手结束的时候
requestDragAndDropPermissions(event) // 申请读取uri的权限
// 处理uri
}
}
true // 返回true代表拖拽事件被处理了
}
3.4.判断拖入的数据类型
拖入方通常有2种方式判断拖入数据类型。
方法1:可以根据event.clipDescription中的MIMETYPE来判断数据类型
MIMETYPE本身是一个字符串,谷歌对其在ClipDescription.java中有一些预定义:
public class ClipDescription implements Parcelable {
/**
* The MIME type for a clip holding plain text.
*/
public static final String MIMETYPE_TEXT_PLAIN = "text/plain";
/**
* The MIME type for a clip holding HTML text.
*/
public static final String MIMETYPE_TEXT_HTML = "text/html";
/**
* The MIME type for a clip holding one or more URIs. This should be
* used for URIs that are meaningful to a user (such as an http: URI).
* It should <em>not</em> be used for a content: URI that references some
* other piece of data; in that case the MIME type should be the type
* of the referenced data.
*/
public static final String MIMETYPE_TEXT_URILIST = "text/uri-list";
/**
* The MIME type for a clip holding an Intent.
*/
public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
/**
* The MIME type for an activity. The ClipData must include intents with required extras
* {@link #EXTRA_PENDING_INTENT} and {@link Intent#EXTRA_USER}, and an optional
* {@link #EXTRA_ACTIVITY_OPTIONS}.
* @hide
*/
public static final String MIMETYPE_APPLICATION_ACTIVITY = "application/vnd.android.activity";
/**
* The MIME type for a shortcut. The ClipData must include intents with required extras
* {@link Intent#EXTRA_SHORTCUT_ID}, {@link Intent#EXTRA_PACKAGE_NAME} and
* {@link Intent#EXTRA_USER}, and an optional {@link #EXTRA_ACTIVITY_OPTIONS}.
* @hide
*/
public static final String MIMETYPE_APPLICATION_SHORTCUT = "application/vnd.android.shortcut";
/**
* The MIME type for a task. The ClipData must include an intent with a required extra
* {@link Intent#EXTRA_TASK_ID} of the task to launch.
app隐私政策生成器 * @hide
*/
public static final String MIMETYPE_APPLICATION_TASK = "application/vnd.android.task";
/**
* The MIME type for data whose type is otherwise unknown.
* <p>
* Per RFC 2046, the "application" media type is to be used for discrete
* data which do not fit in any of the other categories, and the
* "octet-stream" subtype is used to indicate that a body contains arbitrary
* binary data.
*/
public static final String MIMETYPE_UNKNOWN = "application/octet-stream";
......
}
这里面只定义了部分数据,还有其它数据需要app自己定义,目前没有一个准确的规范,通常来说如果是格式为jpg的图像数据则MIMETYPE为”image/jpg”。所以MIMETYPE可以用来初步判定数据类型,但是不完全准确,毕竟MIMETYPE是由拖出方设定的值。
方法2:可以对传入的uri来判断数据类型
我们可以使用ContentResolver的getType方法获取uri的MIMETYPE,然后可以通过MimeTypeMap的getExtensionFromMimeType方法获取文件后缀名。
val mimeType = contentResolver.getType(uri) // 获取MIMETYPE
val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) // 获取文件后缀名
通过文件后缀名我们就可以准确判断数据类型。
编辑:yimen,如若转载,请注明出处:https://www.yimenapp.com/kb-yimen/12491/
部分内容来自网络投稿,如有侵权联系立删