分為三步來說明拖拽是怎么實現的。
1)如何讓拖拽的Item來隨著手指的移動而移動。
2)拖拽過程中相關item的移動處理
3)相關Adapter的是怎么處理的。
1)如何讓拖拽的Item來隨著手指的移動而移動。
在說具體的代碼之前先說說拖拽效果的幾個相關的坐標值,先看下圖:
關于上圖的幾點說明:
1) ev:MotionEvent對象的引用,由于代碼里是在GridView里重寫的onInterceptTouchEvent(MotionEventev)的方法,所以getX()相對的是GridView的位置而不是item的位置。
2) ev.getX():手指觸摸點距離自身控件左邊緣的長度(自身控件在這里為GridView)
ev.getY():手指觸摸點距離自身控件上邊緣的長度(自身控件在這里為GridView)
也就是說getX()和getY()是以自身控件的左上角為(0,0)坐標來計算的。
ev.getRawX():手指觸摸點距離屏幕左邊緣的長度
ev.getRawY():手指觸摸點距離屏幕上邊緣的長度
也就是說getRawX()和getRawY()是以手機屏幕的左上角為(0,0)坐標來計算的
手指拖拽某一個item移動的時候,移動當然涉及到item位置的變化,item會隨著手指的移動而出現在屏幕上的不同位置,具體怎么畫這個位置其實是根據item左上角相對于屏幕的坐標值以及item自身的寬和高來進行繪制的。怎么計算出來手指移動的時候拖拽的那個item相對于左上角相對于屏幕的坐標呢?
觀察上圖,就可以計算出item左上角相對于屏幕的坐標值了。分兩步
1)計算出手機觸摸點相對于item的坐標值(itemViewX,itemViewY)
itemViewX= ev.getX() -item.getLeft();
itemViewY= ev.getY()-item.getTop();
2)item左上角相對于屏幕的坐標值也就是觸摸點距離屏幕左邊的距離和距離屏幕上邊的距離的值。設該坐標值為(x,y)
所以x =ev.getRawX()-itemViewX;y = ev.getRawY()-itemViewY;
獲取拖拽的View
我們知道GridView里面的item都是從相應的adapter獲取的getView方法繪制出來的,但是在這里你不要認為你拖動的就是getView方法返回的那個view,事實上是該view通過相關代碼轉換成的一個ImageView,說白了就是你手指拖拽的那個東東就是一個ImageView。
item轉成ImageView相關轉換的代碼如下所示(該代碼是在onItemLongClick方法里實現的):
ViewGroup dragViewGroup = (ViewGroup) getChildAt(startPosition-?
getFirstVisiblePosition());
dragViewGroup.destroyDrawingCache();
dragViewGroup.setDrawingCacheEnabled(true);
Bitmap?dragBitmap?=?Bitmap.createBitmap(dragViewGroup.getDrawingCache());
startDrag(dragBitmap,?(int)?ev.getRawX(),(int)?ev.getRawY());
startDrag的代碼如下,該方法就是就是把Bitmap轉換成了ImageView,初始化該ImageIView的位置并添加到windowManager中去。
protected void startDrag(Bitmap dragBitmap,int rawX,int rawY) {
windowParams?=new WindowManager.LayoutParams();
windowParams.gravity?=?Gravity.TOP?|?Gravity.LEFT;
//?計算item左上角的坐標值,初始化ImageView所在的位置
windowParams.x?=?rawX?-?itemViewX;
windowParams.y?=?rawY?-?itemViewY;
//?放大dragScale倍,可以設置拖動后的倍數
windowParams.width?=?(int)?(dragScale?*?dragBitmap.getWidth());
windowParams.height?=?(int)?(dragScale?*?dragBitmap.getHeight());
windowParams.flags?=?WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|?WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
|?WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|?WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
windowParams.format?=?PixelFormat.TRANSLUCENT;
windowParams.windowAnimations?=0;
//item生成
ImageView?iv?=newImageView(getContext());
iv.setImageBitmap(dragBitmap);
windowManager?=?(WindowManager)?getContext().getSystemService(
Context.WINDOW_SERVICE);//"window"
windowManager.addView(iv,?windowParams);
//保存生成的imageView
dragImageView?=?iv;
}
既然有startDrag,肯定有stopDrag()方法,不難猜出stopDrag方法主要功能是從windowManager方法中刪除startDrag方法中添加的imageView;
/** 停止的拖動,把之前拖動的那個item從windowManage里面remove掉 **/
private void stopDrag()?{
if(dragImageView?!=null)?{
windowManager.removeView(dragImageView);
dragImageView?=null;
?}
}
階段性小結:到此為止主要是為了說明item怎么轉換成ImageView的,你所拖動的就是這個ImageView,怎么要讓這個ImageView隨著手指的移動而移動呢?下面就具體說明。手指移動響應的是MotionEvent.ACTION_MOVE事件,隨著手指的移動變化的是ImageView左上角焦點的變化。實際上就是ev.getRawX()和ev.getRawY()的變化。通過這兩個值和前面說的itemViewX和itemViewY的值很容易計算出隨著手指的移動ImageView的坐標點的值,并隨時更新窗口就可以了。
private void onDrag(intrawx,intrawy) {
if(dragImageView?!=null)?{
//?設置窗口的透明度
windowParams.alpha?=0.6f;
//?重新計算此時item的x和y坐標的位置
windowParams.x?=?rawx?-?itemViewX;
windowParams.y?=?rawy?-?itemViewY;
//?更新view的布局,也就是重新繪制它的位置
windowManager.updateViewLayout(dragImageView,?windowParams);
}
}
當然這個onDrag方法是在onTouchEvent方法中調用的,代碼如下:
@Override
public boolean onTouchEvent(MotionEvent?ev)?{
if(dragImageView?!=null&&?startPosition?!=?AdapterView.INVALID_POSITION)?{
......
switch(ev.getAction())?{
caseMotionEvent.ACTION_MOVE://?當手勢移動的時候
Log.e(tag,"--on?moving--");
onDrag((int)?ev.getRawX(),?(int)?ev.getRawY());
//移動其他的item此處先省略
.....
break;
caseMotionEvent.ACTION_UP:
//?手指抬起的時候讓drawImageView從windowManage里刪除
stopDrag();
....
requestDisallowInterceptTouchEvent(false);
break;
}
}
returnsuper.onTouchEvent(ev);
}