View的拖拽

166 阅读2分钟

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,效果如下图 image.png

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)