Android拖拽详解

其实实现这种效果有两种方法:

View.startDrag(), 然后给需要监听拖拽的控件setOnDragListener.

ItemTouchHelper,这种实现方法更为简单,具体可参考链接描述

这里我是用的第一种方法,因为感觉第二种方法已经烂大街了。。况且第二种方法只能在RecycleView内部移动。跟其他控件结合的话就爆炸。

具体实现步骤

给RecycleView.ViewHolder实现OnClickListener()方法,长按的时候开始拖动。

拖动的时候给不同的DragEvent做不同的操作。分别有DragEvent.ACTION_DRAG_STARTED(拖动开始时)

DragEvent.ACTION_DRAG_ENTERED(拖动的View进入监听的View时),DragEvent.ACTION_DRAG_LOCATION(拖动的View在监听的View中改变位置时),DragEvent.ACTION_DRAG_EXITED(拖动的View离开在监听的View中时),DragEvent.ACTION_DROP(拖动放下时),DragEvent.ACTION_DRAG_ENDED(拖动结束时)

实现RecleView在拖动中排序

这几步中,最重要的还是第二步:

@Override

    public boolean onDrag(View v, DragEvent event) {


        switch (event.getAction()) {

            case DragEvent.ACTION_DRAG_STARTED:

                //开始时,让拖动的Item变白

                break;

            case DragEvent.ACTION_DRAG_ENTERED:

                //进入时,这个Demo不需要用到

                break;

            case DragEvent.ACTION_DRAG_LOCATION:

                //处理RecycleView的滑动

                //处理Item之间的交换

                break;

            case DragEvent.ACTION_DRAG_EXITED:

            case DragEvent.ACTION_DRAG_ENDED:

                //善后工作               

                break;

        }

        //一定要return true

        return true;

    }

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

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

startDrag

先来看下方法介绍:

/**

     * 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)

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

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

可以看到有四个参数:

ClipData data

其实就是一个封装数据的对象,通过拖放操作传递给接受者。该对象可以存放一个Item的集合,Item可以存放如下数据:

public static class Item {

        final CharSequence mText;

        final String mHtmlText;

        final Intent mIntent;

        Uri mUri;

}

注意到可以存放Intent,因此,通常可以将参数存入intent,然后通过静态方法直接创建ClipData对象:

ClipData clipData = ClipData.newIntent("label", intent);

该数据可以在监听的中的DragEvent获取

ClipData clipData = event.getClipData();

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

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可以拖拽到目标位置了。

AndroidDrag-master        DragDropTwoRecyclerViews-master

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容

  • Android中实现拖拽其实很简单,系统早已经提供了api让我使用,主要用到了View的startDrag(sta...
    胡奚冰阅读 8,067评论 1 6
  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom阅读 2,694评论 0 3
  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,312评论 0 10
  • 上一章 噢,年夜饭 小说·目录 我在很早就看穿了,李铭是一个想在伴侣面前占有绝对地位的男人。 高中的时候...
    一枝花琉璃阅读 245评论 0 0
  • 看到了《妖猫传》,感触颇深。讲的是千年前的事,有人对过去的事儿忘不了,因为弥漫着神秘,所以更加迷人。人们...
    三林工作室阅读 209评论 0 2