HarmonyOS鸿蒙Next中应用拖拽能力适配
HarmonyOS鸿蒙Next中应用拖拽能力适配
应用拖拽能力适配
一.业务简介
拖拽作为基本操作,具有直观、便捷等优点,当前HarmonyOS3.0上,发布了超级中转站功能,应用拖拽起的数据可以暂存至超级中转站中,用户也可以从超级中转站中将暂存的数据拖拽至其他应用。
因此,对于应用来说,可接入拖拽能力,实现数据的便捷分享与接入。
二.拖拽数据构造
1. 拖拽数据结构
拖拽数据使用ClipData进行传递,ClipData有内部类Clipdata.Item,一个ClipData中可包含一个或多个Item。
ClipData构造函数有:
ClipData(CharSequence label, String[] mimeTypes, ClipData.Item item)
ClipData(ClipDescription description, ClipData.Item item)
ClipData(ClipData other)
除第三个构造函数为从另一个ClipData创建一个拷贝外,其余均是从构造ClipData.Item开始。
ClipData.Item中包含4种数据,分为:Text、Intent、Uri以及HtmlText;
ClipDescription中包含对拖拽数据的描述,构造函数为:
ClipDescription(CharSequence label, String[] mimeTypes)
Label可自定义用于标识拖拽数据,如标记数据来源App等;mimeTypes用于描述拖拽数据中的类型,与Item中的数据类型对应的,有四种类型:MIMETYPE_TEXT_PLAIN、MIMETYPE_TEXT_INTENT、MIMETYPE_TEXT_URILIST和MIMETYPE_TEXT_HTML,以及未知类型:MIMETYPE_UNKNOWN。
常用拖拽数据示例
文本数据拖拽示例代码:
private boolean startTextDrag() {
View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(mTextView);
ClipDescription clipDescription = new ClipDescription("label", new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN});
ClipData clipData = new ClipData(clipDescription, new ClipData.Item(mTextView.getText()));
mTextView.startDragAndDrop(clipData, shadowBuilder, null, View.DRAG_FLAG_GLOBAL);
return true;
}
图片、文件等数据拖拽示例代码:
private boolean startFileDrag() {
View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(mDragView);
File filePath = new File(getApplicationContext().getFilesDir(), "drag");
File file = new File(filePath, "drag.jpg");
Uri uri = FileProvider.getUriForFile(getApplicationContext(), "com.example.app.fileprovider", file);
ClipDescription clipDescription = new ClipDescription("label", new String[]{ClipDescription.MIMETYPE_TEXT_URILIST});
ClipData clipData = new ClipData(clipDescription, new ClipData.Item(uri)); // 构造Uri类型的ClipData
mDragView.startDragAndDrop(clipData, shadowBuilder, null, View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ);
return true;
}
卡片类型数据拖拽:
示例代码:
private boolean startCardDrag(View view) {
Intent intent = new Intent();
intent.putExtra("Title", mTitle); // 标题信息
intent.putExtra("Description", mDescription); // 描述信息
intent.putExtra("LinkUrl", mLinkUrl); // 链接地址
intent.putExtra("AppName", "淘宝"); // 来源应用
Drawable thumbResource = ContextCompat.getDrawable(this, R.drawable.ic_thumb_icon);
BitmapDrawable thumbIcon = (BitmapDrawable) thumbResource;
if (thumbIcon == null) {
return false;
}
Bitmap thumbIconBitmap = thumbIcon.getBitmap(); // 生成拖入卡片缩略图bitmap
// 缩略图不能过大,过大需进行压缩
thumbIconBitmap = Bitmap.createScaledBitmap(thumbIconBitmap, 50, 50, true);
intent.putExtra("ThumbData", thumbIconBitmap);
Drawable resource = ContextCompat.getDrawable(this, R.drawable.ic_app_icon); // 来源应用图标
BitmapDrawable appIcon = (BitmapDrawable) resource;
if (appIcon == null) {
return false;
}
final Bitmap appIconBitmap = appIcon.getBitmap();
intent.putExtra("AppIcon", appIconBitmap); // 设置来源应用图标
intent.putExtra("ContentType", 1); // 设置为1,卡片类型
ClipData.Item item = new ClipData.Item(mTitle + "\n" + mDescription + "\n" + mLinkUrl, null, intent, null);
mMimeTypes.add(0, "text/vnd.android.intent"); // 设置为intent类型
ClipDescription clipDescription = new ClipDescription("HwDragDropLabel", mMimeTypes.toArray(new String[0]));
ClipData clipData = new ClipData(clipDescription, item); // 构造拖拽数据ClipData
View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view); // 设置拖拽浮层
view.startDragAndDrop(clipData, shadowBuilder, null, View.DRAG_FLAG_GLOBAL);
return true; // 发起拖拽
}
三.应用拖出能力适配
拖拽行为一般与长按操作进行绑定,即通过长按触发拖拽。
通过长按触发拖拽可使用两种方案:
- 使用系统默认长按逻辑
对于需要提供拖拽能力的控件,通过添加长按系统默认长按监听setOnLongClickListener(callback())来对长按事件进行响应,callback()中进行上述DragShadowBuilder以及ClipData的构造,并最终通过startDragAndDrop()发起拖拽。
以TextView为例,mTextView为一个TextView实例,示例代码如下:
private void initLongClickListener() {
if (mTextView == null) {
return;
}
mTextView.setOnLongClickListener(view -> startTextDrag()); // 设置长按监听
}
private boolean startTextDrag() {
View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(mTextView);
ClipDescription clipDescription = new ClipDescription("label", new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN});
ClipData clipData = new ClipData(clipDescription, new ClipData.Item(mTextView.getText()));
mTextView.startDragAndDrop(clipData, shadowBuilder, null, View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ);
return true;
}
- 使用自定义长按逻辑
对于部分应用而言,长按本身已经绑定了某些操作,如弹出浮窗等,此时无法通过系统默认长按监听实现拖拽,因此可以进行自定义长按逻辑实现。
对于需要进行拖拽的控件,需在控件中重写dispatchDragEvent(MotionEvent event)方法,重写方法后,可以在控件中收到分发的MotionEvent事件。
MotionEvent事件中,常用事件为ACTION_DOWN、ACTION_MOVE、ACTION_UP和ACTION_CANCEL,其中对于判断长按行为最关键的是ACTION_DOWN和ACTION_MOVE事件。
当首次手指按压屏幕时,会下发ACTION_DOWN事件,当手指移动时,会下发ACTION_MOVE事件,由于手指按压会有轻微移动,实际上会不断下发ACTION_MOVE,因此可通过DOWN和连续的MOVE事件对长按行为进行检测:
长按的判断需结合位置和时间两个元素,通过长按事件响应不同的操作;位置判断用于减少误判,由于MotionEvent事件中携带了事件的位置信息,因此可以通过事件坐标的变化来判断。
通过:
private final int mTouchSlop = ViewConfiguration.get(getApplicationContext()).getScaledTouchSlop()
可以获取系统的滑动常量,即手指移动阈值,当手指移动的距离小于此阈值时,判断为持续长按;当手指移动的距离大于此阈值时,判断为手指滑动。
示例代码如下:
private long mStartPressTime = 0; // 记录首次按压的时间
private long mLongPressDuration = 0; // 记录长按时长
private double mXPos = 0.0; // 记录首次x坐标信息
private double mYPos = 0.0; // 记录首次y坐标信息
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mXPos = event.getRawX(); // DOWN事件时,记录首次坐标信
mYPos = event.getRawY();
mStartPressTime = event.getEventTime(); // 记录首次按压时间
return true;
case MotionEvent.ACTION_MOVE:
double currentX = event.getRawX(); // MOVE时间,记录MOVE坐标
double currentY = event.getRawY();
// 更新长按时长
mLongPressDuration = event.getEventTime() - mStartPressTime;
// 判断当前是否是长按(根据坐标变化),且判断时长是否满足条件
if (Math.abs(currentX - mXPos) <= mTouchSlop && Math.abs(currentY - mYPos) <= mTouchSlop
&& mLongPressDuration > 1000) {
return startDrag(); // 进行指定操作,如:拖拽
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: // 其他事件重置长按发起时间
mStartPressTime = 0;
}
return super.dispatchTouchEvent(event);
}
根据事件的时间长短,可以区分不同时长的长按,如:500ms弹出PopupMenu,1000ms收起PopupMenu并发起拖拽,核心逻辑在重写的dispatchTouchEvent()的ACTION_MOVE事件中,示例代码如下:
case MotionEvent.ACTION_MOVE:
double currentX = event.getRawX(); // MOVE时间,记录MOVE坐标
double currentY = event.getRawY();
// 更新长按时长
mLongPressDuration = event.getEventTime() - mStartPressTime;
// 判断是否长按(根据坐标变化),且判断时长和PopupMenu是否显示
if (Math.abs(currentX - mXPos) <= mTouchSlop && Math.abs(currentY - mYPos) <= mTouchSlop
&& mLongPressDuration > 500 && !isPopShow) {
showPopup(); // 进行指定操作:弹出PopupMenu
isPopShow = true; // 可写入showPopup()方法中
}
// 当长按时长大于1000ms时
if (Math.abs(currentX - mXPos) <= mTouchSlop && Math.abs(currentY - mYPos) <= mTouchSlop && mLongPressDuration > 1000) {
if (isPopShow) {
hidePopup(); // 收起PopupMenu
isPopShow = false; // 可写入hidePopup()方法中
}
startDrag(); // 开始拖拽
}
break;
由此,可以在原有应用长按逻辑基础上,新增对于长按拖拽的适配。
四.应用拖入能力适配
对于应用来说,当提供了数据拖出能力后,相应的应当提供接收拖入数据的能力,以保证用户体验的一致性。
接收拖入数据,主要在于对控件注册拖拽监听,即setOnDragListener(callback()),注册拖拽监听后,应用则能够接收到拖拽事件DragEvent,DragEvent由系统侧下发,应用不需关注其具体实现,在注册拖拽监听后,在回调函数中关注DragEvent.ACTION_DROP事件。
常用的DragEvent事件有ACTION_DRAG_STARTED、ACTION_DRAG_ENTERED、ACTION_DRAG_LOCATION、ACTION_DRAG_EXITED、ACTION_DRAG_ENDED和ACTION_DROP,分别对应拖拽的发起、拖拽内容进入控件、拖拽内容在空间内移动、拖拽移出控件、拖拽结束和拖拽drop。
在ACTION_DROP中,可以从拖拽事件中获取到拖拽数据ClipData,从而获取到拖拽的内容,应用获取拖拽数据后,可以自行对拖拽数据进行处理,示例代码如下:
private void setListeners() {
// 注册拖拽监听
mDropView.setOnDragListener((view, dragEvent) -> canHandleDrag(dragEvent));
}
private boolean canHandleDrag(DragEvent dragEvent) {
if (dragEvent == null) {
return false;
}
int action = dragEvent.getAction();
switch (action) {
case DragEvent.ACTION_DROP: // 当收到ACTION_DROP事件时,处理数据
return hasHandleDrop(dragEvent);
default:
// todo:处理其他拖拽事件
}
return true;
}
private boolean hasHandleDrop(DragEvent dragEvent) {
ClipData clipData = dragEvent.getClipData(); // 从拖拽事件中获取数据
if (clipData == null || clipData.getItemCount() == 0) {
return false;
}
// todo:对数据做进一步处理
}
更多关于HarmonyOS鸿蒙Next中应用拖拽能力适配的实战教程也可以访问 https://www.itying.com/category-93-b0.html
感谢分享
学习
在HarmonyOS鸿蒙Next中,应用拖拽能力适配主要依赖于DragEvent
和DragListener
接口。开发者需在布局文件中为可拖拽组件设置onDragListener
,并在onDrag
方法中处理拖拽事件。通过DragEvent.ACTION_DRAG_STARTED
、DragEvent.ACTION_DRAG_ENTERED
等事件类型,实现拖拽开始、进入、离开等逻辑。同时,使用ClipData
传递拖拽数据,确保数据在拖拽过程中的一致性。适配时需注意不同设备的屏幕尺寸和分辨率,确保拖拽体验的流畅性和一致性。