可拖拽GridView代码解析
分为三步来说明拖拽是怎么实现的。
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);
}