Android拖拽詳解

其實(shí)實(shí)現(xiàn)這種效果有兩種方法:

View.startDrag(), 然后給需要監(jiān)聽拖拽的控件setOnDragListener.

ItemTouchHelper,這種實(shí)現(xiàn)方法更為簡(jiǎn)單,具體可參考鏈接描述

這里我是用的第一種方法,因?yàn)楦杏X第二種方法已經(jīng)爛大街了。。況且第二種方法只能在RecycleView內(nèi)部移動(dòng)。跟其他控件結(jié)合的話就爆炸。

具體實(shí)現(xiàn)步驟

給RecycleView.ViewHolder實(shí)現(xiàn)OnClickListener()方法,長(zhǎng)按的時(shí)候開始拖動(dòng)。

拖動(dòng)的時(shí)候給不同的DragEvent做不同的操作。分別有DragEvent.ACTION_DRAG_STARTED(拖動(dòng)開始時(shí))

DragEvent.ACTION_DRAG_ENTERED(拖動(dòng)的View進(jìn)入監(jiān)聽的View時(shí)),DragEvent.ACTION_DRAG_LOCATION(拖動(dòng)的View在監(jiān)聽的View中改變位置時(shí)),DragEvent.ACTION_DRAG_EXITED(拖動(dòng)的View離開在監(jiān)聽的View中時(shí)),DragEvent.ACTION_DROP(拖動(dòng)放下時(shí)),DragEvent.ACTION_DRAG_ENDED(拖動(dòng)結(jié)束時(shí))

實(shí)現(xiàn)RecleView在拖動(dòng)中排序

這幾步中,最重要的還是第二步:

@Override

? ? public boolean onDrag(View v, DragEvent event) {


? ? ? ? switch (event.getAction()) {

? ? ? ? ? ? case DragEvent.ACTION_DRAG_STARTED:

? ? ? ? ? ? ? ? //開始時(shí),讓拖動(dòng)的Item變白

? ? ? ? ? ? ? ? break;

? ? ? ? ? ? case DragEvent.ACTION_DRAG_ENTERED:

? ? ? ? ? ? ? ? //進(jìn)入時(shí),這個(gè)Demo不需要用到

? ? ? ? ? ? ? ? break;

? ? ? ? ? ? case DragEvent.ACTION_DRAG_LOCATION:

? ? ? ? ? ? ? ? //處理RecycleView的滑動(dòng)

? ? ? ? ? ? ? ? //處理Item之間的交換

? ? ? ? ? ? ? ? break;

? ? ? ? ? ? case DragEvent.ACTION_DRAG_EXITED:

? ? ? ? ? ? case DragEvent.ACTION_DRAG_ENDED:

? ? ? ? ? ? ? ? //善后工作? ? ? ? ? ? ? ?

? ? ? ? ? ? ? ? break;

? ? ? ? }

? ? ? ? //一定要return true

? ? ? ? return true;

? ? }

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

Android中實(shí)現(xiàn)拖拽其實(shí)很簡(jiǎn)單,系統(tǒng)早已經(jīng)提供了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)

看到英文就頭大?沒事,我來翻譯解釋一下。

啟動(dòng)拖放操作。當(dāng)應(yīng)用程序調(diào)用此方法時(shí),它將傳遞一個(gè)DragShadowBuilder對(duì)象到系統(tǒng)。系統(tǒng)調(diào)用此對(duì)象的onProvideShadowMetrics(Point, Point)方法獲取拖動(dòng)陰影的參數(shù)指標(biāo),然后調(diào)用onDrawShadow(Canvas)來繪制陰影。一旦系統(tǒng)有了拖動(dòng)陰影,它就開始拖拽操作,通過將拖拽事件發(fā)送到當(dāng)前可見的應(yīng)用程序中的所有視圖對(duì)象。這些視圖可以通過設(shè)置OnDragListener在或者實(shí)現(xiàn)onDragEvent方法接受DragEvent(事件)來響應(yīng)和拖拽事件。

可以看到有四個(gè)參數(shù):

ClipData data

其實(shí)就是一個(gè)封裝數(shù)據(jù)的對(duì)象,通過拖放操作傳遞給接受者。該對(duì)象可以存放一個(gè)Item的集合,Item可以存放如下數(shù)據(jù):

public?static?class Item {

????????final?CharSequence mText;

????????final?String mHtmlText;

????????final?Intent mIntent;

????????Uri mUri;

}

注意到可以存放Intent,因此,通常可以將參數(shù)存入intent,然后通過靜態(tài)方法直接創(chuàng)建ClipData對(duì)象:

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

該數(shù)據(jù)可以在監(jiān)聽的中的DragEvent獲取

ClipData clipData = event.getClipData();

簡(jiǎn)單點(diǎn)說就是可以將一些數(shù)據(jù)傳遞給拖拽的接受者,該拖拽其實(shí)可以跨Activity的,如果只是同一個(gè)Activity可以使用第三個(gè)參數(shù)傳遞數(shù)據(jù)。

DragShadowBuilder shadowBuilder

用于創(chuàng)建拖拽view是的陰影,也就是跟隨手指移動(dòng)的視圖,通常直接使用默認(rèn)即可生成與一個(gè)原始view相同,帶有透明度的陰影:

View.DragShadowBuilder shadowBuilder = new?View.DragShadowBuilder(v);

Object myLocalState

當(dāng)你的拖拽行為是在同一個(gè)Activity中進(jìn)行時(shí)可以傳遞一個(gè)任意對(duì)象,在監(jiān)聽中可以通過{@link android.view.DragEvent#getLocalState()}獲得。如果是跨Activity拖拽中無法訪問此數(shù)據(jù),getLocalState()將返回null。

int flags

控制拖放操作的標(biāo)志。因?yàn)闆]有標(biāo)志可以設(shè)置為0,flag標(biāo)志拖動(dòng)是否可以跨越窗口以及一些訪問權(quán)限(需要API24+)。

了解了方法參數(shù)含義,接下來就是啟用拖拽了,通常會(huì)通過長(zhǎng)按來觸發(fā)拖拽:

iv.setOnLongClickListener(new?View.OnLongClickListener() {

????????????@Override

????????????public boolean onLongClick(View v) {

????????????????View.DragShadowBuilder shadowBuilder = new?View.DragShadowBuilder(v);

????????????????v.startDrag(null, shadowBuilder, null, 0);

????????????????//震動(dòng)反饋

????????????????v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);

????????????????return?true;

????????????}

????????});

開始拖拽后還要有來接受這些拖拽事件,這就需要OnDragListener了。

OnDragListener

OnDragListener是在View中定義的接口,用于響應(yīng)拖拽事件,可以通過View的setOnDragListener方法設(shè)置監(jiān)聽,有點(diǎn)類似于點(diǎn)擊事件。

public?interface OnDragListener {

????????boolean onDrag(View v, DragEvent event);

}

設(shè)置監(jiān)聽,實(shí)現(xiàn)onDrag(View v, DragEvent event)方法,其中View是設(shè)置該監(jiān)聽的view,DragEvent是拖拽事件,可以通過event.getAction()獲取具體事件類型,這和TouchEvent非常類似,具體事件類型有如下幾種:

fl_blue.setOnDragListener(new?View.OnDragListener() {

????????????@Override

????????????public boolean onDrag(View v, DragEvent event) {

????????????????//v 永遠(yuǎn)是設(shè)置該監(jiān)聽的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, "結(jié)束拖拽");

????????????????????????break;

????????????????????case?DragEvent.ACTION_DRAG_ENTERED:

????????????????????????Log.i(BLUE, "拖拽的view進(jìn)入監(jiān)聽的view時(shí)");

????????????????????????break;

????????????????????case?DragEvent.ACTION_DRAG_EXITED:

????????????????????????Log.i(BLUE, "拖拽的view離開監(jiān)聽的view時(shí)");

????????????????????????break;

????????????????????case?DragEvent.ACTION_DRAG_LOCATION:

????????????????????????float?x = event.getX();

????????????????????????float?y = event.getY();

????????????????????????long?l = SystemClock.currentThreadTimeMillis();

????????????????????????Log.i(BLUE, "拖拽的view在監(jiān)聽view中的位置:x ="?+ x + ",y="?+ y);

????????????????????????break;

????????????????????case?DragEvent.ACTION_DROP:

????????????????????????Log.i(BLUE, "釋放拖拽的view");

????????????????????????break;

????????????????}

????????????????//是否響應(yīng)拖拽事件,true響應(yīng),返回false只能接受到ACTION_DRAG_STARTED事件,后續(xù)事件不會(huì)收到

????????????????return?true;

????????????}

????????});

此處通過event.getX(); event.getY();獲取的x,y是手指(也即是被拖拽view的中心點(diǎn))在監(jiān)聽view的位置。

釋放手指會(huì)觸發(fā)ACTION_DRAG_ENDED事件,如果此時(shí)被拖拽的view正好在監(jiān)聽的view中,則會(huì)先觸發(fā)ACTION_DROP事件。

這里寫圖片描述

可以同時(shí)有多個(gè)view設(shè)置拖拽監(jiān)聽接受事件,我給紅色和藍(lán)色view都設(shè)置了OnDragListener,然后拖動(dòng)Android圖片到藍(lán)色區(qū)域后釋放,可以看到日志如下:

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進(jìn)入監(jiān)聽的view時(shí)

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:結(jié)束拖拽

03-09 14:53:56.017?12937-12937/com.huburt.app.androiddrag I/BLUE:結(jié)束拖拽

現(xiàn)在我們已經(jīng)可以把Android圖片拖出來,但是還不能把它放入目標(biāo)view,其實(shí)也挺簡(jiǎn)單的,只需要在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;

這里因?yàn)槭窃谕粋€(gè)Activity中,我是將拖拽的view直接傳遞過來了,當(dāng)然也可以只傳遞圖片,然后在接收的view中重新new一個(gè)imageview現(xiàn)實(shí)圖片。

運(yùn)行一下就可以看到view可以拖拽到目標(biāo)位置了。

AndroidDrag-master ? ? ? ?DragDropTwoRecyclerViews-master

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android中實(shí)現(xiàn)拖拽其實(shí)很簡(jiǎn)單,系統(tǒng)早已經(jīng)提供了api讓我使用,主要用到了View的startDrag(sta...
    胡奚冰閱讀 8,390評(píng)論 1 6
  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom閱讀 3,158評(píng)論 0 3
  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi閱讀 7,841評(píng)論 0 10
  • 上一章 噢,年夜飯 小說·目錄 我在很早就看穿了,李銘是一個(gè)想在伴侶面前占有絕對(duì)地位的男人。 高中的時(shí)候...
    一枝花琉璃閱讀 280評(píng)論 0 0
  • 看到了《妖貓傳》,感觸頗深。講的是千年前的事,有人對(duì)過去的事兒忘不了,因?yàn)閺浡衩兀愿用匀?。人?..
    三林工作室閱讀 259評(píng)論 0 2

友情鏈接更多精彩內(nèi)容