Android拖拽详解

Android中实现拖拽其实很简单,系统早已经提供了api让我使用,主要用到了View的startDrag(startDragAndDrop API24+) 方法以及OnDragListenerjava

startDrag

先来看下方法介绍:android

/**
     * Starts a drag and drop operation. When your application calls this method, it passes a
     * {@link android.view.View.DragShadowBuilder} object to the system. The
     * system calls this object's {@link DragShadowBuilder#onProvideShadowMetrics(Point, Point)} * to get metrics for the drag shadow, and then calls the object's
     * {@link DragShadowBuilder#onDrawShadow(Canvas)} to draw the drag shadow itself.
     * <p>
     *  Once the system has the drag shadow, it begins the drag and drop operation by sending
     *  drag events to all the View objects in your application that are currently visible. It does
     *  this either by calling the View object's drag listener (an implementation of * {@link android.view.View.OnDragListener#onDrag(View,DragEvent) onDrag()} or by calling the * View object's {@link android.view.View#onDragEvent(DragEvent) onDragEvent()} method.
     *  Both are passed a {@link android.view.DragEvent} object that has a
     *  {@link android.view.DragEvent#getAction()} value of
     *  {@link android.view.DragEvent#ACTION_DRAG_STARTED}.
     * </p>
     * <p>
     * Your application can invoke {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object,
     * int) startDragAndDrop()} on any attached View object. The View object does not need to be
     * the one used in {@link android.view.View.DragShadowBuilder}, nor does it need to be related
     * to the View the user selected for dragging.
     * </p>
     * @param data A {@link android.content.ClipData} object pointing to the data to be
     * transferred by the drag and drop operation.
     * @param shadowBuilder A {@link android.view.View.DragShadowBuilder} object for building the
     * drag shadow.
     * @param myLocalState An {@link java.lang.Object} containing local data about the drag and
     * drop operation. When dispatching drag events to views in the same activity this object
     * will be available through {@link android.view.DragEvent#getLocalState()}. Views in other
     * activities will not have access to this data ({@link android.view.DragEvent#getLocalState()}
     * will return null).
     * <p>
     * myLocalState is a lightweight mechanism for the sending information from the dragged View
     * to the target Views. For example, it can contain flags that differentiate between a
     * a copy operation and a move operation.
     * </p>
     * @param flags Flags that control the drag and drop operation. This can be set to 0 for no
     * flags, or any combination of the following:
     *     <ul>
     *         <li>{@link #DRAG_FLAG_GLOBAL}</li>
     *         <li>{@link #DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION}</li>
     *         <li>{@link #DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION}</li>
     *         <li>{@link #DRAG_FLAG_GLOBAL_URI_READ}</li>
     *         <li>{@link #DRAG_FLAG_GLOBAL_URI_WRITE}</li>
     *         <li>{@link #DRAG_FLAG_OPAQUE}</li>
     *     </ul>
     * @return {@code true} if the method completes successfully, or
     * {@code false} if it fails anywhere. Returning {@code false} means the system was unable to
     * do a drag, and so no drag operation is in progress.
     */
    public final boolean startDragAndDrop(ClipData data, DragShadowBuilder shadowBuilder,Object myLocalState, int flags) 
复制代码

看到英文就头大?没事,我来翻译解释一下。git

启动拖放操做。当应用程序调用此方法时,它将传递一个DragShadowBuilder对象到系统。系统调用此对象的onProvideShadowMetrics(Point, Point)方法获取拖动阴影的参数指标,而后调用onDrawShadow(Canvas)来绘制阴影。一旦系统有了拖动阴影,它就开始拖拽操做,经过将拖拽事件发送到当前可见的应用程序中的全部视图对象。这些视图能够经过设置OnDragListener在或者实现onDragEvent方法接受DragEvent(事件)来响应和拖拽事件。github

能够看到有四个参数:api

ClipData databash

其实就是一个封装数据的对象,经过拖放操做传递给接受者。该对象能够存放一个Item的集合,Item能够存放以下数据:app

public static class Item {
        final CharSequence mText;
        final String mHtmlText;
        final Intent mIntent;
        Uri mUri;
}
复制代码

注意到能够存放Intent,所以,一般能够将参数存入intent,而后经过静态方法直接建立ClipData对象:ide

ClipData clipData = ClipData.newIntent("label", intent);
复制代码

该数据能够在监听的中的DragEvent获取ui

ClipData clipData = event.getClipData();
复制代码

简单点说就是能够将一些数据传递给拖拽的接受者,该拖拽其实能够跨Activity的,若是只是同一个Activity可使用第三个参数传递数据。this

DragShadowBuilder shadowBuilder

用于建立拖拽view是的阴影,也就是跟随手指移动的视图,一般直接使用默认便可生成与一个原始view相同,带有透明度的阴影:

View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(v);
复制代码

Object myLocalState

当你的拖拽行为是在同一个Activity中进行时能够传递一个任意对象,在监听中能够经过{@link android.view.DragEvent#getLocalState()}得到。若是是跨Activity拖拽中没法访问此数据,getLocalState()将返回null。

int flags

控制拖放操做的标志。由于没有标志能够设置为0,flag标志拖动是否能够跨越窗口以及一些访问权限(须要API24+)。

了解了方法参数含义,接下来就是启用拖拽了,一般会经过长按来触发拖拽:

iv.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(v);
                v.startDrag(null, shadowBuilder, null, 0);
                //震动反馈
                v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
                return true;
            }
        });
复制代码

开始拖拽后还要有来接受这些拖拽事件,这就须要OnDragListener了。

OnDragListener

OnDragListener是在View中定义的接口,用于响应拖拽事件,能够经过View的setOnDragListener 方法设置监听,有点相似于点击事件。

public interface OnDragListener { 
        boolean onDrag(View v, DragEvent event);
}
复制代码

设置监听,实现onDrag(View v, DragEvent event)方法,其中View是设置该监听的view,DragEvent是拖拽事件,能够经过event.getAction() 获取具体事件类型,这和TouchEvent很是相似,具体事件类型有以下几种:

fl_blue.setOnDragListener(new View.OnDragListener() {
            @Override
            public boolean onDrag(View v, DragEvent event) {
                //v 永远是设置该监听的view,这里即fl_blue
                String simpleName = v.getClass().getSimpleName();
                Log.w(BLUE, "view name:" + simpleName);
                
                //获取事件
                int action = event.getAction();
                switch (action) {
                    case DragEvent.ACTION_DRAG_STARTED:
                        Log.i(BLUE, "开始拖拽");
                        break;
                    case DragEvent.ACTION_DRAG_ENDED:
                        Log.i(BLUE, "结束拖拽");
                        break;
                    case DragEvent.ACTION_DRAG_ENTERED:
                        Log.i(BLUE, "拖拽的view进入监听的view时");
                        break;
                    case DragEvent.ACTION_DRAG_EXITED:
                        Log.i(BLUE, "拖拽的view离开监听的view时");
                        break;
                    case DragEvent.ACTION_DRAG_LOCATION:
                        float x = event.getX();
                        float y = event.getY();
                        long l = SystemClock.currentThreadTimeMillis();
                        Log.i(BLUE, "拖拽的view在监听view中的位置:x =" + x + ",y=" + y);
                        break;
                    case DragEvent.ACTION_DROP:
                        Log.i(BLUE, "释放拖拽的view");
                        break;
                }
                //是否响应拖拽事件,true响应,返回false只能接受到ACTION_DRAG_STARTED事件,后续事件不会收到
                return true;
            }
        });
复制代码

此处经过event.getX(); event.getY(); 获取的x,y是手指(也便是被拖拽view的中心点)在监听view的位置。

释放手指会触发ACTION_DRAG_ENDED 事件,若是此时被拖拽的view正好在监听的view中,则会先触发ACTION_DROP 事件。

这里写图片描述

能够同时有多个view设置拖拽监听接受事件,我给红色和蓝色view都设置了OnDragListener,而后拖动Android图片到蓝色区域后释放,能够看到日志以下:

03-09 14:53:54.518 12937-12937/com.huburt.app.androiddrag I/RED: 开始拖拽
03-09 14:53:54.518 12937-12937/com.huburt.app.androiddrag I/BLUE: 开始拖拽
03-09 14:53:55.689 12937-12937/com.huburt.app.androiddrag I/BLUE: 拖拽的view进入监听的view时
03-09 14:53:55.689 12937-12937/com.huburt.app.androiddrag I/BLUE: 拖拽的view在BLUE中的位置:x =111.0,y=2.0
03-09 14:53:55.870 12937-12937/com.huburt.app.androiddrag I/BLUE: 拖拽的view在BLUE中的位置:x =112.0,y=23.0
03-09 14:53:56.014 12937-12937/com.huburt.app.androiddrag I/BLUE: 释放拖拽的view
03-09 14:53:56.017 12937-12937/com.huburt.app.androiddrag I/RED: 结束拖拽
03-09 14:53:56.017 12937-12937/com.huburt.app.androiddrag I/BLUE: 结束拖拽
复制代码

如今咱们已经能够把Android图片拖出来,可是还不能把它放入目标view,其实也挺简单的,只须要在ACTION_DROP事件作一些处理便可:

case DragEvent.ACTION_DROP:
                        Log.i(BLUE, "释放拖拽的view");
                        ImageView localState = (ImageView) event.getLocalState();
                        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                        layoutParams.topMargin = (int) event.getY() - localState.getWidth() / 2;
                        layoutParams.leftMargin = (int) event.getX() - localState.getHeight() / 2;
                        ((ViewGroup) localState.getParent()).removeView(localState);
                        fl_blue.addView(localState, layoutParams);
                        break;
复制代码

这里由于是在同一个Activity中,我是将拖拽的view直接传递过来了,固然也能够只传递图片,而后在接收的view中从新new一个imageview现实图片。

运行一下就能够看到view能够拖拽到目标位置了。

可能文字描述不是特别清楚,请看demo

相关文章
相关标签/搜索