今天偶然在apkbus上看到了以下欄目拖拽功能,我們也化繁為簡散休,一步一步來簡單實現(xiàn)划址。
第一步夺颤,實現(xiàn)拖拽胁勺;
第二步署穗,拖拽后的動畫;
通過這篇文章可以了解的內(nèi)容:
第一封恰,view的拖拽俭驮;
第二春贸,屬性動畫的執(zhí)行逸嘀;
第三崭倘,view的位置計算(各種相對位置,比如相對屏幕琅坡,相對父view等等)
第四榆俺,拖拽影像的產(chǎn)生茴晋。
第一步實現(xiàn)拖拽
通過對其源碼的分析,實現(xiàn)拖拽的主要用法就是如下代碼:
private static final ClipData EMPTY_CLIP_DATA = ClipData.newPlainText("", "");
@Override
public boolean onLongClick(View v) {
mTvDrag.startDrag(EMPTY_CLIP_DATA,new View.DragShadowBuilder(),mTvDrag,0);
return false;
}
注意這里只是設(shè)置在某個事件后(比如這里長按事件)開始拖拽烁涌,這以后撮执,如果我們按著view進行拖拽的話二打,那么拖拽監(jiān)聽將可以監(jiān)控到掂榔,雖然此時view不能拖動。
那我們?nèi)绾卧O(shè)置監(jiān)聽呢穴豫?上代碼:
mTvDrag.setOnDragListener(this);
設(shè)置監(jiān)聽還有另外一種方式,就是調(diào)用了startDrag方法的view的父類中去實現(xiàn)onDragEvent()
方法逼友,這樣也可以監(jiān)聽到view的拖拽精肃。
注意點:
誰負責(zé)監(jiān)聽,那么這個監(jiān)聽的view就是可拖拽的范圍帜乞,這個很重要司抱,因為這涉及到拖拽時坐標(biāo)的計算
@Override
public boolean onDragEvent(DragEvent event) {
int action = event.getAction();
// 拖拽點x和y坐標(biāo)
int eventX = (int) event.getX();
int eventY = (int) event.getY();
switch (action) {
// 拖拽開始監(jiān)聽
case DragEvent.ACTION_DRAG_STARTED:
break;
// 拖拽進入時監(jiān)聽,可以開始進行拖拽
// 和STARTED區(qū)別稍后講
case DragEvent.ACTION_DRAG_ENTERED:
Log.d("zp_test", "ENTERED " + event.getY());
break;
// 拖拽進行中
case DragEvent.ACTION_DRAG_LOCATION:
break;
// 拖拽結(jié)束
case DragEvent.ACTION_DRAG_ENDED:
case DragEvent.ACTION_DRAG_EXITED:
break;
// 拖拽松開
case DragEvent.ACTION_DROP:
break;
}
return true;
}
也許你會講黎烈,直接用onTouchListener和layout方法也可以實現(xiàn)view跟隨手指一起滑動跋澳匀谣!沒錯是可以,但是用它來實現(xiàn)更為復(fù)雜的內(nèi)容時资溃,那將是場災(zāi)難武翎,比如長按后拖動溶锭,點擊事件不被屏蔽等等,實現(xiàn)起來就顯得稍微麻煩了露久。
第二步繪制影像陰影
細心的你一定能夠發(fā)現(xiàn)迟几,在拖拽的時候,拖拽的view是透明度比本身的view要低的一個影像缸逃。拖拽實現(xiàn)的原理其實是:在可拖拽區(qū)域放置一個framelayout昭殉,在framelayout中有一個隱藏的imageview卢厂,當(dāng)你長按哪個可拖動的view的時候礁阁,獲取到這個view的位置越走,然后通過復(fù)制這個view生成一個bitmap對象復(fù)制給framelayout中隱藏的imageview骡澈。然后根據(jù)手指位置來設(shè)置隱藏的imageview(此時顯示這個imageview)位置。
由此可見官地,所謂的拖拽其實是在拖拽一個影像亏较,影像是最先設(shè)置在framelayout中的一個imageview癣朗。通過不斷更新imageview的setX和setY來進行拖動正卧。
通過一個view來獲取其cache背景圖叉讥,從而生成一個跟其一模一樣的bitmap對象救崔。
private Bitmap createDraggedChildBitmap(View view) {
view.setDrawingCacheEnabled(true);
final Bitmap cache = view.getDrawingCache();
Bitmap bitmap = null;
if (cache != null) {
try {
bitmap = cache.copy(Bitmap.Config.ARGB_8888, false);
} catch (final OutOfMemoryError e) {
Log.w("zp_test", "Failed to copy bitmap from Drawing cache", e);
bitmap = null;
}
}
view.destroyDrawingCache();
view.setDrawingCacheEnabled(false);
return bitmap;
}
至于計算當(dāng)前view的位置,那么就需要你先了解以下這些內(nèi)容:
第一點
getTop(),getLeft(),getRight(),getBottom()
這四個方法是指相對于父view的位置,并且一般情況下它的值是不會發(fā)生變化的,除非有屬性動畫改變其位置,或者重新進行l(wèi)ayout方法的調(diào)用。
第二點
在拖拽監(jiān)聽中int eventX = (int) event.getX();int eventY = (int) event.getY();
不同拖拽事件對應(yīng)的eventx和eventy是不同的丹诀,具體如下:STARTED事件下對應(yīng)是拖拽點與屏幕邊界的距離(包括狀態(tài)欄)枚荣,ENTERED對應(yīng)是拖拽點與界面邊界的距離(不包括狀態(tài)欄)矢劲、LOCATION笋籽、DROP事件下對應(yīng)的是拖拽點和父view邊界的距離侍芝,END時為0。
第三點
當(dāng)進行拖拽時:
依次執(zhí)行started虱肄,entered萝挤,若干個location欺税,然后drop情组,end這樣一個執(zhí)行順序。
如下圖:
接下來我們開工:
我們長按一個view,然后在同樣的位置生成一個影像奶赔。這個工作適合在started事件中去完成温艇。
case DragEvent.ACTION_DRAG_STARTED:
Log.d("zp_test", "STARTED " + event.getY() + " x: " + event.getX());
if (mDrayListener != null) {
// 7.0以下eventX,eventY是相對于屏幕邊界線的
View drag = getViewFromPositionRelativeScreen(eventX, eventY);
if (drag == null) {
Log.e("zp_test", "drag is null");
break;
}
int[] location = new int[2];
drag.getLocationInWindow(location);
mPointToBorderX = eventX - location[0];
mPointToBorderY = eventY - location[1];
mDrayListener.onDragStart(createDraggedChildBitmap(drag), drag);
}
break;
private int[] location = new int[2];
private View getViewFromPositionRelativeScreen(int x, int y) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
getLocationInWindow(location);
return getViewFromPositionRelativeFather(x - location[0], y - location[1]);
} else {
return getViewFromPositionRelativeFather(x, y);
}
}
private View getViewFromPositionRelativeFather(int x, int y) {
Log.d("zp_test", "x: " + x + " y: " + y);
int childCount = getChildCount();
Log.d("zp_test", "childCount: " + childCount);
if (childCount <= 0)
return null;
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
Log.d("zp_test", "view l : " + view.getX()
+ " view r: " + view.getX() + view.getWidth()
+ " view t: " + view.getY()
+ " view b: " + view.getY() + view.getHeight());
// 根據(jù)點擊位置獲取view
if (y >= view.getY() && y <= view.getY() + view.getHeight()
&& x >= view.getX() && x <= view.getX() + view.getWidth())
return view;
}
return null;
}
上面兩個方法就可以從點擊點獲取到點擊在哪個view上误阻。
注意以下兩句代碼很重要
mPointToBorderX = eventX - location[0]; mPointToBorderY = eventY - location[1];
這個是在求得點擊點和被拖拽的view邊界的距離卦停,因為我們要繪制影像的位置凿跳,就必須知道被拖拽的view到其父view的位置,這個就是影像的位置博助,但是我們從監(jiān)聽方法中只能知道點擊點的坐標(biāo)位置萝喘,我們還得求得這個view邊界到父view的位置醉旦,也就是得減去點擊點到拖拽view邊界的距離
影像y坐標(biāo) = (點擊點y坐標(biāo)) - (點擊點到view邊界的距離)
最開始我沒有做這個計算队秩,所以導(dǎo)致每次從STARTED事件到ENTERED事件時,都會跳動一下泽艘,而這個跳動的距離實際上就是mPointToBorderX 和mPointToBorderY 。
到這里,我們就實現(xiàn)了拖拽功能了沃于,而后的功能在下篇進行介紹繁莹,最后薄风,因為只有幾個類就不分享到github了撇他,給出百度鏈接:
拖拽功能code.zip
如有錯誤芭毙,歡迎指出。(本人最近已離職锭魔,在上海如有工作推薦例证,麻煩各位留言或者私信我,謝謝大家C耘酢)