1.简介
- 在学习launcher的taskbar上的hotseat拖拽分屏的时候看到方法startDragAndDrop,发现是View里的,就简单学习下
- 完事复习下ViewDragHelper的用法
1.1.简单使用
>1.拖放一个button到另外一个容器。
findViewById<View>(R.id.btn_drag_view).setOnLongClickListener {
testDrag(it)
return@setOnLongClickListener false
}
//给容器设置监听,处理DROP事件,给容器里添加一个按钮
findViewById<LinearLayout>(R.id.layout_container).setOnDragListener { v, event ->
println("mytest=======$v===$event")
when (event.action) {
DragEvent.ACTION_DROP -> {
val clipData = event.clipData
val btn = Button(this)
btn.setText(clipData.getItemAt(0).text)//按照索引获取item
(v as LinearLayout).addView(btn)
}
}
return@setOnDragListener true
}
private fun testDrag(dragView: View) {
val item = ClipData.Item("simple item")
val item2 = ClipData.Item("simple html", getString(R.string.simple_html))
val item3 = ClipData.Item(Intent(this, MyTestActivity::class.java))
val item4 = ClipData.Item(Uri.parse("http://www.baidu.com"))
val item5 = ClipData.Item("item bing",
Intent(Intent.ACTION_VIEW).setData(Uri.parse("http://www.bing.com")),
Uri.parse("http://www.bing.com"))
//设置传递的数据mime类型
val mimeTypes = arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN,
ClipDescription.MIMETYPE_TEXT_HTML,
ClipDescription.MIMETYPE_TEXT_INTENT,
ClipDescription.MIMETYPE_TEXT_URILIST)
//ClipData里可以添加多个item
val dragData = ClipData("testClipData", mimeTypes, item)
dragData.addItem(item2)
dragData.addItem(item3)
dragData.addItem(item4)
dragData.addItem(item5)
//拖放开始后跟随手指显示的视图就是通过这个创建的
val shadow = DragShadowBuilder(dragView)
//开始拖放
val result = dragView.startDragAndDrop(dragData, shadow, null, View.DRAG_FLAG_GLOBAL)
println("mytest=======testDrag result=$result")
}
- 长按事件可以修改为如下的代码,这个封装了长按和触摸事件
DragStartHelper(findViewById(R.id.btn_drag_view)) { v, helper ->
testDrag(v)
true
}.attach()
>2.拖放显示一个activity或者task
代码参考3.2 到3.4,效果如下图
findViewById<View>(R.id.btn_drag_split).setOnLongClickListener {
testSplit(true, it)
return@setOnLongClickListener false
}
private fun testSplit(shortCutTest: Boolean, dragView: View) {
var intent = Intent()
val shadowBuilder = DragShadowBuilder(dragView)
val clipDescription = ClipDescription("item.title",
arrayOf(if (shortCutTest) "application/vnd.android.shortcut" else "application/vnd.android.activity"))
val userHandle = UserHandle.getUserHandleForUid(0)
if (shortCutTest) {
val pkg = "com.android.settings"
val deepShortcutId = "manifest-shortcut-battery"
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, pkg)
intent.putExtra(Intent.EXTRA_SHORTCUT_ID, deepShortcutId)
} else {
intent.putExtra("android.intent.extra.PENDING_INTENT",
PendingIntent.getActivity(this,
111,
packageManager.getLaunchIntentForPackage("com.android.settings"),
PendingIntent.FLAG_IMMUTABLE));
}
intent.putExtra(Intent.EXTRA_USER, userHandle)
val clipData = ClipData(clipDescription, ClipData.Item(intent))
Binder.clearCallingIdentity()
if (dragView.startDragAndDrop(clipData,
shadowBuilder,
null /* localState */,
View.DRAG_FLAG_GLOBAL or View.DRAG_FLAG_OPAQUE)) {
println("mytest=========startDragAndDrop true")
} else {
println("mytest=========startDragAndDrop failed")
}
}
2.View
2.1.startDragAndDrop
- 开启拖放操作,当你的应用程序调用这个方法时,它传递一个DragShadowBuilder对象添加到系统。系统调用该对象的onProvideShadowMetrics(Point, Point)来获取拖动阴影的度量,然后调用该对象的onDrawShadow(Canvas)来绘制拖动阴影本身。
- 一旦系统有了拖放阴影,它就会通过向应用程序中当前可见的所有View对象发送拖放事件来开始拖放操作。
- 它通过调用View对象的拖动监听器的onDrag方法或调用View对象的onDragEvent()方法来实现这一点。两者都传递了一个DragEvent对象,该对象的getAction()值为ACTION_DRAG_STARTED,参考2.3
- 您的应用程序可以在任何已添加的视图对象上调用startDragAndDrop(),View对象不需要是DragShadowBuilder中使用的对象,它也不需要与用户选择拖动的视图相关
- 直白点说,我可以拖拽控件A,然后调用控件B的startDragAndDrop方法,DragShadowBuilder对象里传递控件C
- data: 拖放要传递的数据
- shadowBuilder :上边有讲,会调用它里边的2个方法构建拖放阴影
- myLocalState:首先只有在同一个activity里才有效,可以在拖放的时候传递本地数据,通过回调里的DragEvent的getLocalState()方法获取
- flags:标记,可以参考2.2
public final boolean startDragAndDrop(ClipData data, DragShadowBuilder shadowBuilder,
Object myLocalState, int flags) {
if (ViewDebug.DEBUG_DRAG) {
Log.d(VIEW_LOG_TAG, "startDragAndDrop: data=" + data + " flags=" + flags);
}
if (mAttachInfo == null) {
Log.w(VIEW_LOG_TAG, "startDragAndDrop called on a detached view.");
return false;
}
if (!mAttachInfo.mViewRootImpl.mSurface.isValid()) {
Log.w(VIEW_LOG_TAG, "startDragAndDrop called with an invalid surface.");
return false;
}
if (data != null) {
data.prepareToLeaveProcess((flags & View.DRAG_FLAG_GLOBAL) != 0);
}
Rect bounds = new Rect();
getBoundsOnScreen(bounds, true);
Point lastTouchPoint = new Point();
mAttachInfo.mViewRootImpl.getLastTouchPoint(lastTouchPoint);
final ViewRootImpl root = mAttachInfo.mViewRootImpl;
// Skip surface logic since shadows and animation are not required during the a11y drag
final boolean a11yEnabled = AccessibilityManager.getInstance(mContext).isEnabled();
if (a11yEnabled && (flags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) != 0) {
try {
IBinder token = mAttachInfo.mSession.performDrag(
mAttachInfo.mWindow, flags, null,
mAttachInfo.mViewRootImpl.getLastTouchSource(),
0f, 0f, 0f, 0f, data);
if (ViewDebug.DEBUG_DRAG) {
Log.d(VIEW_LOG_TAG, "startDragAndDrop via a11y action returned " + token);
}
if (token != null) {
root.setLocalDragState(myLocalState);
mAttachInfo.mDragToken = token;
mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this);
setAccessibilityDragStarted(true);
}
return token != null;
} catch (Exception e) {
Log.e(VIEW_LOG_TAG, "Unable to initiate a11y drag", e);
return false;
}
}
Point shadowSize = new Point();
Point shadowTouchPoint = new Point();
shadowBuilder.onProvideShadowMetrics(shadowSize, shadowTouchPoint);
if ((shadowSize.x < 0) || (shadowSize.y < 0)
|| (shadowTouchPoint.x < 0) || (shadowTouchPoint.y < 0)) {
throw new IllegalStateException("Drag shadow dimensions must not be negative");
}
// Create 1x1 surface when zero surface size is specified because SurfaceControl.Builder
// does not accept zero size surface.
if (shadowSize.x == 0 || shadowSize.y == 0) {
if (!sAcceptZeroSizeDragShadow) {
throw new IllegalStateException("Drag shadow dimensions must be positive");
}
shadowSize.x = 1;
shadowSize.y = 1;
}
if (ViewDebug.DEBUG_DRAG) {
Log.d(VIEW_LOG_TAG, "drag shadow: width=" + shadowSize.x + " height=" + shadowSize.y
+ " shadowX=" + shadowTouchPoint.x + " shadowY=" + shadowTouchPoint.y);
}
final SurfaceSession session = new SurfaceSession();
final SurfaceControl surfaceControl = new SurfaceControl.Builder(session)
.setName("drag surface")
.setParent(root.getSurfaceControl())
.setBufferSize(shadowSize.x, shadowSize.y)
.setFormat(PixelFormat.TRANSLUCENT)
.setCallsite("View.startDragAndDrop")
.build();
final Surface surface = new Surface();
surface.copyFrom(surfaceControl);
IBinder token = null;
try {
final Canvas canvas = isHardwareAccelerated()
? surface.lockHardwareCanvas()
: surface.lockCanvas(null);
try {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
shadowBuilder.onDrawShadow(canvas);
} finally {
surface.unlockCanvasAndPost(canvas);
}
token = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, flags, surfaceControl,
root.getLastTouchSource(), lastTouchPoint.x, lastTouchPoint.y,
shadowTouchPoint.x, shadowTouchPoint.y, data);
if (ViewDebug.DEBUG_DRAG) {
Log.d(VIEW_LOG_TAG, "performDrag returned " + token);
}
if (token != null) {
if (mAttachInfo.mDragSurface != null) {
mAttachInfo.mDragSurface.release();
}
mAttachInfo.mDragSurface = surface;
mAttachInfo.mDragToken = token;
// Cache the local state object for delivery with DragEvents
root.setLocalDragState(myLocalState);
if (a11yEnabled) {
// Set for AccessibilityEvents
mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this);
}
}
return token != null;
} catch (Exception e) {
Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e);
return false;
} finally {
if (token == null) {
surface.destroy();
}
session.kill();
}
}
2.2.DRAG_FLAG
DRAG_FLAG_GLOBAL
DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION
DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION
DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION
DRAG_FLAG_GLOBAL_URI_READ
DRAG_FLAG_GLOBAL_URI_WRITE
DRAG_FLAG_OPAQUE
>1.DRAG_FLAG_GLOBAL
- 指示拖动可以跨越窗口边界的标志。当startDragAndDrop被调用并设置此标志时,所有具有targetSdkVersion >= API 24的可见应用程序将能够参与拖动操作并接收拖动的内容。
- 如果这是唯一的标志集,那么拖动接收者将只能访问ClipData对象中包含的文本数据和意图。对ClipData中包含的uri的访问由其他DRAG_FLAG_GLOBAL_标志决定
public static final int DRAG_FLAG_GLOBAL = 1 << 8; // 256
>2.DRAG_FLAG_GLOBAL_URI_READ
- 当此标志与DRAG_FLAG_GLOBAL一起使用时,拖动接收方将能够请求对ClipData对象中包含的内容URI的读访问权
public static final int DRAG_FLAG_GLOBAL_URI_READ = Intent.FLAG_GRANT_READ_URI_PERMISSION;
>3.DRAG_FLAG_GLOBAL_URI_WRITE
- 当此标志与DRAG_FLAG_GLOBAL一起使用时,拖动接收方将能够请求对ClipData对象中包含的内容URI的写访问
public static final int DRAG_FLAG_GLOBAL_URI_WRITE = Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
>4.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION
- 当此标志与DRAG_FLAG_GLOBAL_URI_READ/DRAG_FLAG_GLOBAL_URI_WRITE一起使用时,URI权限授予可以在设备重启期间持续存在,直到使用Context.revokeUriPermission显式撤销
public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION =
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
>5.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION
- 当此标志与DRAG_FLAG_GLOBAL_URI_READ和/或DRAG_FLAG_GLOBAL_URI_WRITE一起使用时,URI权限授予适用于与原始授予的URI前缀匹配的任何URI
public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION =
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
>6.DRAG_FLAG_OPAQUE
- 指示拖动阴影不透明的标志。当startDragAndDrop被调用并设置此标志时,拖动阴影将是不透明的,否则,它将是半透明的
public static final int DRAG_FLAG_OPAQUE = 1 << 9;
2.3.dispatchDragEvent
- ViewGroup里会调用,分发逻辑就不看了。
public boolean dispatchDragEvent(DragEvent event) {
event.mEventHandlerWasCalled = true;
if (event.mAction == DragEvent.ACTION_DRAG_LOCATION ||
event.mAction == DragEvent.ACTION_DROP) {
//即将传递带有坐标的事件到此视图。通知现在这个视图有了拖动焦点。这将根据需要发送退出/进入事件
getViewRootImpl().setDragFocus(this, event);
}
return callDragEventHandler(event);
}
>1.callDragEventHandler
final boolean callDragEventHandler(DragEvent event) {
final boolean result;
ListenerInfo li = mListenerInfo;
//有设置拖放监听器,并且回调里返回ture的走if,监听器的设置参考补充2
if (li != null && li.mOnDragListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnDragListener.onDrag(this, event)) {
result = true;
} else {//否则走这里,参考2.4
result = onDragEvent(event);
}
switch (event.mAction) {
case DragEvent.ACTION_DRAG_STARTED: {
if (result && li != null && li.mOnDragListener != null) {
sendWindowContentChangedAccessibilityEvent(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
} break;
case DragEvent.ACTION_DRAG_ENTERED: {
mPrivateFlags2 |= View.PFLAG2_DRAG_HOVERED;
refreshDrawableState();
} break;
case DragEvent.ACTION_DRAG_EXITED: {
mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED;
refreshDrawableState();
} break;
case DragEvent.ACTION_DROP: {
if (result && li != null && (li.mOnDragListener != null
|| li.mOnReceiveContentListener != null)) {
sendWindowContentChangedAccessibilityEvent(
AccessibilityEvent.CONTENT_CHANGE_TYPE_DRAG_DROPPED);
}
} break;
case DragEvent.ACTION_DRAG_ENDED: {
sendWindowContentChangedAccessibilityEvent(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
mPrivateFlags2 &= ~View.DRAG_MASK;
refreshDrawableState();
} break;
}
return result;
}
>2.setOnDragListener
public void setOnDragListener(OnDragListener l) {
getListenerInfo().mOnDragListener = l;
}
2.4.onDragEvent
public boolean onDragEvent(DragEvent event) {
if (mListenerInfo == null || mListenerInfo.mOnReceiveContentListener == null) {
//需要接收内容监听器
return false;
}
// 如果设置了OnReceiveContentListener,则默认接受拖动事件
if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) {
return true;
}
if (event.getAction() == DragEvent.ACTION_DROP) {
final DragAndDropPermissions permissions = DragAndDropPermissions.obtain(event);
if (permissions != null) {
permissions.takeTransient();
}
final ContentInfo payload =
new ContentInfo.Builder(event.getClipData(), SOURCE_DRAG_AND_DROP)
.setDragAndDropPermissions(permissions)
.build();
ContentInfo remainingPayload = performReceiveContent(payload);//补充1
// 返回true,除非负载没有被消耗
return remainingPayload != payload;
}
return false;
}
>1.performReceiveContent
- 接收给定的内容。如果没有设置监听器,则调用onReceiveContent。
- 如果设置了侦听器,则调用侦听器;如果侦听器返回非空结果,则调用onReceiveContent来处理它
- payload :要插入的内容和相关元数据
- 返回 传入内容中未被接受的部分(可能是全部、部分或没有传入内容)
public ContentInfo performReceiveContent(@NonNull ContentInfo payload) {
final OnReceiveContentListener listener = (mListenerInfo == null) ? null
: getListenerInfo().mOnReceiveContentListener;
if (listener != null) {
final ContentInfo remaining = listener.onReceiveContent(this, payload);
return (remaining == null) ? null : onReceiveContent(remaining);
}
return onReceiveContent(payload);//补充2
}
>2.onReceiveContent
public ContentInfo onReceiveContent(@NonNull ContentInfo payload) {
return payload;
}
2.5.DragShadowBuilder
静态内部类,这里看下默认实现,可以自定义。
public static class DragShadowBuilder {
>1.构造方法
public DragShadowBuilder(View view) {
mView = new WeakReference<View>(view);
}
>2.onProvideShadowMetric
- outShadowSize:拖放开始后要显示的视图尺寸
- outShadowTouchPoint:视图的触摸位置
public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) {
final View view = mView.get();
if (view != null) {
outShadowSize.set(view.getWidth(), view.getHeight());
outShadowTouchPoint.set(outShadowSize.x / 2, outShadowSize.y / 2);
}
}
>3.onDrawShadow
- 视图默认显示的就是构造方法里传递的view。
public void onDrawShadow(Canvas canvas) {
final View view = mView.get();
if (view != null) {
view.draw(canvas);
}
}
3.ClipDescription.java
3.1.文本类型的MIME_TYPE
public static final String MIMETYPE_TEXT_PLAIN = "text/plain";
public static final String MIMETYPE_TEXT_HTML = "text/html";
public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
public static final String MIMETYPE_TEXT_URILIST = "text/uri-list";
>1.案例
val item = ClipData.Item("simple item")
val mimeTypes = arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN)
val dragData = ClipData("testClipData", mimeTypes, item)
val shadow = DragShadowBuilder(dragView)
val result = dragView.startDragAndDrop(dragData, shadow, null, View.DRAG_FLAG_GLOBAL)
3.2.activity类型的MIME_TYPE
>1.MIMETYPE_APPLICATION_ACTIVITY
- 类型是一个活动页面
- ClipData必须包含Intent和额外的EXTRA_PENDING_INTENT和Intent。EXTRA_USER和可选的EXTRA_ACTIVITY_OPTIONS
public static final String MIMETYPE_APPLICATION_ACTIVITY = "application/vnd.android.activity";
>2.案例
- ClipData需要intent数据,intent里必须有EXTRA_PENDING_INTENT参数,里边是一个PendingIntent
- 还必须有EXTRA_USER参数
- EXTRA_ACTIVITY_OPTIONS参数是可选的
var intent = Intent()
intent.putExtra("android.intent.extra.PENDING_INTENT",
PendingIntent.getActivity(this,
111,
packageManager.getLaunchIntentForPackage("com.android.settings"),
PendingIntent.FLAG_IMMUTABLE));
intent.putExtra(Intent.EXTRA_USER, userHandle)
val clipData = ClipData(clipDescription, ClipData.Item(intent))
3.3.shortcut类型的MIME_TYPE
>1.MIMETYPE_APPLICATION_SHORTCUT
- 快捷方式类型
- ClipData必须包含Intent。
- 而Intent必须的3个参数 EXTRA_SHORTCUT_ID,EXTRA_PACKAGE_NAME 和 EXTRA_USER ,可选的EXTRA_ACTIVITY_OPTIONS
public static final String MIMETYPE_APPLICATION_SHORTCUT = "application/vnd.android.shortcut";
>2.案例
intent.putExtra("android.intent.extra.PENDING_INTENT",
launcherApps.getShortcutIntent(pkg, deepShortcutId, null, userHandle))
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, pkg)
intent.putExtra(Intent.EXTRA_SHORTCUT_ID, deepShortcutId)
3.4.task类型的MIME_TYPE
>1.MIMETYPE_APPLICATION_TASK
- ClipData里需要参数EXTRA_TASK_ID
public static final String MIMETYPE_APPLICATION_TASK = "application/vnd.android.task";
>2.案例
val clipDescription = ClipDescription("task item", arrayOf("application/vnd.android.task"))
var intent = Intent()
intent.putExtra("android.intent.extra.TASK_ID", 47);
val clipData = ClipData(clipDescription, ClipData.Item(intent))
>3.获取task id
adb shell dumpsys activity activities
日志如下,那个#后边的就是id
(fullscreen) Task{da6d663 #43 type=standard A=10191:com.example.myapp2023 U=0 visible=false visibleRequested=false mode=fullscreen translucent=true sz=1}
(fullscreen) Task{e393f0b #47 type=standard A=10118:com.android.chrome U=0 visible=true visibleRequested=true mode=fullscreen translucent=false sz=1}
3.ViewDragHelper
3.1.demo
- create方法的第一个参数,就是可以拖拽的容器,也就是它里边的child可以被拖拽
package com.example.myapp2023.custom
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.widget.FrameLayout
import androidx.annotation.Px
import androidx.customview.widget.ViewDragHelper
import androidx.customview.widget.ViewDragHelper.EDGE_RIGHT
import com.example.myapp2023.R
class MyViewGroup2 @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
FrameLayout(context, attrs) {
var viewDragHelper: ViewDragHelper = ViewDragHelper.create(this,object : ViewDragHelper.Callback(){
override fun tryCaptureView(child: View, pointerId: Int): Boolean {
println("mytest=======tryCaptureView===${pointerId}==${child}")
return true
}
override fun onViewDragStateChanged(state: Int) {
println("mytest====onViewDragStateChanged==$state")
}
override fun onViewPositionChanged(changedView: View, left: Int, top: Int, @Px dx: Int, @Px dy: Int) {
println("mytest=======onViewPositionChanged===$left/$top ,dx:dy=$dx/$dy")
}
override fun onViewCaptured(capturedChild: View, activePointerId: Int) {
println("mytest=======onViewCaptured===$activePointerId")
}
override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) {
println("mytest=======onViewReleased===")
}
override fun onEdgeTouched(edgeFlags: Int, pointerId: Int) {
println("mytest=======onEdgeTouched==$edgeFlags ,$pointerId")
}
override fun onEdgeLock(edgeFlags: Int): Boolean {
println("mytest=======onEdgeLock===$edgeFlags")
return false
}
override fun onEdgeDragStarted(edgeFlags: Int, pointerId: Int) {
println("mytest=======onEdgeDragStarted===")
}
override fun getOrderedChildIndex(index: Int): Int {
println("mytest=======getOrderedChildIndex==="+index)
return index
}
override fun getViewHorizontalDragRange(child: View): Int {
println("mytest=======getViewHorizontalDragRange===")
return width-child.width
}
override fun getViewVerticalDragRange(child: View): Int {
println("mytest=======getViewVerticalDragRange===")
return height-child.height
}
override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int {
println("mytest=======clampViewPositionHorizontal===left:$left ,dx:$dx")
return Math.max(0,Math.min(left,getViewHorizontalDragRange(child)))
}
override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int {
println("mytest=======clampViewPositionVertical===top:$top ,dy:$dy")
return Math.max(0,Math.min(top,getViewVerticalDragRange(child)))
}
})
init {
viewDragHelper.setEdgeTrackingEnabled(EDGE_RIGHT)
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
return viewDragHelper.shouldInterceptTouchEvent(ev)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
viewDragHelper.processTouchEvent(event)
return super.onTouchEvent(event)
}
}
3.2.Callback
>1.tryCaptureView
- 需要重写的方法,返回true表示这个child可以拖动,这里可以过滤哪些child需要拖动
- tryCaptureView 》 onViewCaptured 》 onViewDragStateChanged
override fun tryCaptureView(child: View, pointerId: Int): Boolean {
return true
}
>2.getViewHorizontalDragRange
- child水平或者垂直方向可以拖动的范围,默认值是0,无法拖动的
- 参考源码,感觉只要设置个大于0的就行,主要影响一个duration,当然最好还是返回你认为的应该拖动的范围值
override fun getViewHorizontalDragRange(child: View): Int {
return width-child.width
}
override fun getViewVerticalDragRange(child: View): Int {
return height-child.height
}
>3.clampViewPositionHorizontal
- 这个也是必须重写的方法,默认值是0,无法移动的
- 参数dx是横向滑动的距离,left是滑动后的位置,返回值就是最终用户决定的child应该显示的位置。
- 下边是限制了只能在容器范围内移动,所以左右上下都做了判定,如果不判定,你直接返回left或者top就行
override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int {
println("mytest=======clampViewPositionHorizontal===left:$left ,dx:$dx")
return Math.max(0,Math.min(left,getViewHorizontalDragRange(child)))
}
override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int {
println("mytest=======clampViewPositionVertical===top:$top ,dy:$dy")
return Math.max(0,Math.min(top,getViewVerticalDragRange(child)))
}
>4.onEdgeTouched
- 这里是控制4个边界的触摸事件的,不过有个前提
override fun onEdgeTouched(edgeFlags: Int, pointerId: Int) {
//点击允许的边界并且没有可以捕获的child的时候会走这里
}
override fun onEdgeLock(edgeFlags: Int): Boolean {
println("mytest=======onEdgeLock===$edgeFlags")
return false
}
override fun onEdgeDragStarted(edgeFlags: Int, pointerId: Int) {
println("mytest=======onEdgeDragStarted===")
}
必须先设置允许哪些边界可以被侦测,否则上边的方法不会执行
viewDragHelper.setEdgeTrackingEnabled(EDGE_RIGHT)