天天看点

可拖拽GridView代码解析

本片学习笔记是对eoe网上一个项目代码的解读。详细项目作者的博客例如以下:http://blog.csdn.net/vipzjyno1/article/details/26514543。项目源代码下载地址为https://github.com/Rano1/TopNews本篇仅仅对可拖拽的GridView的代码进行解读,同一时候改动了原项目中不必要的变量、去掉了不必要或者逻辑错误的代码,也删除了方法中不必要的局部变量和计算。通过对这个读这个代码。自己也着实学到了不少的东西(毕竟自己刚接触android不久。特别是还从来没有接触过手机端的开发,都是从事着机顶盒方法的apk开发).

本文准备分为三步来说明拖拽是怎么实现的。

 1)怎样让拖拽的Item来随着手指的移动而移动。

 2)拖拽过程中相关item的移动处理

 3)相关Adapter的是怎么处理的。

以下详细进行说明

初始化的GridViewd的效果图如图1:

可拖拽GridView代码解析

                                          图1

如果手指拖动的是J这个item,在处理中对某一个item运行长按事件,那么就意味着选中了这个item。然后就能够拖拽着这个item进行移动了。

  拖动后的效果图如图2所看到的:

可拖拽GridView代码解析

                                             图2

在说详细的代码之前先说说拖拽效果的几个相关的坐标值,先看下图:

可拖拽GridView代码解析

                                              图3 

关于上图的几点说明:

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相对于左上角相对于屏幕的坐标呢?以下就详细说说计算的方法。先看例如以下图例:

可拖拽GridView代码解析

                                                              图4

观察上图。就能够计算出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;

阶段性小结:这样获取拖拽的item相对于屏幕的坐标(x,y),这个左边点非常重要。如上所说,以上乱七八糟的说了这么多基本上就是在说这个item的坐标点怎么计算

所以在Java里面用下面几个字段来保存这几种坐标的值

我们知道GridView里面的item都是从对应的adapter获取的getView方法绘制出来的,可是在这里你不要觉得你拖动的就是getView方法返回的那个view,其实是该view通过相关代码转换成的一个ImageView。说白了就是你手指拖拽的那个东东就是一个ImageView.当然在代码里又得加入了一个全局变量来存储这个ImageView.

item转成ImageView相关转换的代码例如以下所看到的(该代码是在onItemLongClick方法里实现的):

startDrag的代码例如以下。该方法就是就是把Bitmap转换成了ImageView,初始化该ImageIView的位置并加入到windowManager中去。

既然有startDrag,肯定有stopDrag()方法。不难猜出stopDrag方法主要功能是从windowManager方法中删除startDrag方法中加入的imageView;

阶段性小结:到此为止主要是为了说明item怎么转换成ImageView的。你所拖动的就是这个ImageView,怎么要让这个ImageView随着手指的移动而移动呢?以下就详细说明。

手指移动响应的是MotionEvent.ACTION_MOVE事件。随着手指的移动变化的是ImageView左上角焦点的变化。

实际上就是ev.getRawX()和ev.getRawY()的变化。通过这两个值和前面说的itemViewX和itemViewY的值非常easy计算出随着手指的移动ImageView的坐标点的值,并随时更新窗体就能够了。

所以代码例如以下所看到的

当然这个onDrag方法是在onTouchEvent方法中调用的,代码例如以下:

到此为止,第一部分怎样让item随着手指的移动而移动说完了。以下讲第二部分

  相关item指的什么?它们是怎么移动的?详细说明之前先做几个说明。手指移动的方向相对于所拖拽的item原来的位置分为三种:

 如图示(如果拖拽的那个item为J):

可拖拽GridView代码解析

1) 向左向右水平移动的情况非常easy,比方J向左移动到I所在的位置。那么I和J位置对调就能够了,此时J所在行的顺序为J I KL。

J向右移动到L的情况就是J先和K转换一下位置,然后在和L换一下位置。此时j所在行的顺序为I K L J.

2) 让J移动到第二行F所在的位置。移动后的效果图例如以下:

可拖拽GridView代码解析

 和移动之前的对照,会发现F G H I都发生了水平移动,F G I三个item在自己所在的行向右水平移动了一个位置。

而H这个item是个移动的情况就比較特殊了:竖直(y)方向上看H从第二行移动到了第三行,说明y的坐标值相对于原来的位置变大了一倍;水平(x)方向上看H从第四列移动到了第三列,说明x的坐标值相对于原来的位置变小了,确切得说是减小了三倍。

   注意此时有四个Item參与了移动。移动多少个item是通过计算得到的。

     移动的item个数movecount =起始item位置-目的item位置。

     在这里起始item位置就是I所在的位置,由onItemLongClick(AdapterView<?

> parent, View view, intposition, long id方法的第三个參数来决定,并由全局变量startPosition来存储;目的item位置就是J所在的位置,由pointToPosition依据手指所在的位置类计算得到。并用全局变量dropPosition来存储。(注意此时movecount<0);

3)让J移动到第四行N所在的位置,移动后的效果例如以下图所看到的:

可拖拽GridView代码解析

   跟移动之前的效果对照,会发现此时K L M N发生了水平向左移动,当中K L N三个item在自己所在的行向左水平移动了一个位置。而M这个item比較特殊:竖直(y)方向上看,由第四行上移到了第三行,说明y的坐标现对于移动前的值降低了一倍。水平(x)方向上看,由第一列变成了第四列,说明x的位置相对于移动之前的位置变大了三倍。

(移动的item个数movecount>0);综上说明代码中移动的方法onMove就不难理解了,代码例如以下:

onMove调用的地方时例如以下:

到此位置第二部分相关item移动的处理已经说完了

所以adapter提供了exchange方法来实现数据的交换

该方法详细调用的地方是其最后一个item动画效果完毕后运行

由此,对可拖拽的GridView的实现说明所有结束。以下贴上我改动过后GridView和Adapter的所有代码

GridView代码例如以下:

所以getX()获取的是相对于GirdView左上角的坐标

gridViewX = (int) ev.getX();

gridViewY = (int) ev.getY();

// 监听长按事件

setOnItemClickListener(ev);

}

return super.onInterceptTouchEvent(ev);

/*

* 长按点击监听

*/

private void setOnItemClickListener(final MotionEvent ev) {

setOnItemLongClickListener(new OnItemLongClickListener() {

@Override

public boolean onItemLongClick(AdapterView<?> parent, View view,

int position, long id) {

// 假设位置有效

if (position != AdapterView.INVALID_POSITION) {

// 记录第一次点击的位置

startPosition = position;// 開始拖动item的位置

// 获取当前位置的ViewGroup或者item

ViewGroup dragViewGroup = (ViewGroup) getChildAt(startPosition

- getFirstVisiblePosition());

TextView dragTextView = (TextView) dragViewGroup

.findViewById(R.id.text_item);

//设置拖动item的样式

dragTextView.setSelected(true);

dragTextView.setEnabled(false);

dragViewGroup.destroyDrawingCache();

dragViewGroup.setDrawingCacheEnabled(true);

Bitmap dragBitmap = Bitmap.createBitmap(dragViewGroup

.getDrawingCache());

startDrag(dragBitmap, (int) ev.getRawX(),

(int) ev.getRawY());

// 获取当前item的宽和高

itemHeight = dragViewGroup.getHeight();

itemWidth = dragViewGroup.getWidth();

// 屏幕上的x和y dragViewGroup.getLeft()是当前item相对于父控件GirdView的间距

itemViewX = gridViewX - dragViewGroup.getLeft();// item相对自己左上角的x值。以自己的view的左上角为(0,0)

itemViewY = gridViewY - dragViewGroup.getTop();// item相对自己的左上角

// 设置震动时间

mVibrator.vibrate(50);

// 開始拖动getRawX()获取相对于屏幕左上角的位置

// 隐藏当前的item

dragViewGroup.setVisibility(View.INVISIBLE);

requestDisallowInterceptTouchEvent(true);

return true;

return false;

});

public boolean onTouchEvent(MotionEvent ev) {

if (dragImageView != null

&& startPosition != AdapterView.INVALID_POSITION) {

int x = (int) ev.getX();

int y = (int) ev.getY();

switch (ev.getAction()) {

case MotionEvent.ACTION_MOVE:// 当手势移动的时候

Log.e(tag, "--on moving--");

onDrag((int) ev.getRawX(), (int) ev.getRawY());

//移动其它的item

if(!isMoving){

onMove(x, y);

break;

case MotionEvent.ACTION_UP:

// 手指抬起的时候让drawImageView从windowManage里删除

stopDrag();

onDrop(x,y);

requestDisallowInterceptTouchEvent(false);

return super.onTouchEvent(ev);

/**

* 手指抬起时,让你拖拽的那个item显示

* @param x

* @param y

private void onDrop(int x, int y) {

dropPosition = pointToPosition(x, y);

//剩下的交给adapter处理

DragGridAdapter dragAdapter = (DragGridAdapter)getAdapter();

dragAdapter.setShowDropItem(true);

dragAdapter.notifyDataSetChanged();

/** 在ScrollView内。所以要进行计算高度 */

public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,MeasureSpec.AT_MOST);

super.onMeasure(widthMeasureSpec, expandSpec);

* 移动的时候触发,移动处理其它的item

*

* 手指相对于当前view也就是GirdView 横坐标的位置

* 手指相对于当前view也就是GirdView纵坐标的位置

public void onMove(int x, int y) {

// 推断当前的item的位置,或者说推断当前位置是那一个item

// 或者说是当前手指的位置

int dPosition = pointToPosition(x, y);

// 注意,本应用第一行的前两个item是不能拖动的

Log.e(tag, "--dPosition--" + dPosition);

if (dPosition > 1) {

// 假设如今手势的位置==你拖拽開始的位置

if (dPosition == startPosition) {

return;

// 放下的位置或者手指移动后不动的位置

dropPosition = dPosition;

// 须要移动的item的数量

int movecount;

// 拖动的==開始拖的,而且拖动的不等于放下的

if (startPosition != dropPosition) {

// 当前手指的位置减去開始移动时候的那个位置

movecount = dropPosition - startPosition;

ViewGroup dragGroup = (ViewGroup) getChildAt(startPosition);

dragGroup.setVisibility(View.INVISIBLE);

int adsMovCount = Math.abs(movecount);

float to_x;// 当前下方position

float to_y;// 当前下方右边position

// 移动距离的百分比(相对于自己宽度的百分比)

float x_value = ((float) mHorizontalSpacing / (float) itemWidth) + 1.0f;

// 移动距离的百分比(相对于自己长度度的百分比)

float y_value = ((float) mVerticalSpacing / (float) itemHeight) + 1.0f;

// 循环遍历每一个

for (int i = 0; i < adsMovCount; i++) {

to_x = x_value;

to_y = y_value;

// 假设手指是像右或者向下一行移动,那么其余的item就像左移动

if (movecount > 0) {

// 挨个推断要移动item与被拖拽的item是否是同一行的

// //相对于被拖拽的item右边要移动的第i个item的position

holdPosition = startPosition + i + 1;

if (startPosition / nColumns == holdPosition / nColumns) {// 是dragImageView同一行

//与dragViewImageView 同行的向水平左移动

to_x = -x_value;

to_y = 0;

} else if (holdPosition % 4 == 0) {//dragImageView所在行的下一行的第一列

to_x = 3 * x_value;//x的位置移动到dragImageView行的最后一列,所以位置要*3

to_y = -y_value;//向上移动一行

} else {

//dragViewImageView行下一行的水平移动

} else {// 假设手指是向左或者上一行移动,那么其余的item就向右移动

// 相对于被拖拽的item左边要移动的第i个item的position

holdPosition = startPosition - i - 1;

// 同行item的向右移动

} else if ((holdPosition + 1) % 4 == 0) {//dragImageView的上一行的最后一列

to_x = -3 * x_value;//x的位置移动到dragImageView所在行的第一列

to_y = y_value;//向下移动一行

//dragViewImageView所在行上一行的水平移动

}// end if-else

// 获取第i个要移动的item

ViewGroup moveViewGroup = (ViewGroup) getChildAt(holdPosition);

Animation animation = getMoveAnimation(to_x, to_y);

moveViewGroup.startAnimation(animation);

// 假设是最后一个移动的。那么设置他的最后动画id为lastAdimationId

if (holdPosition == dropPosition) {

LastAnimationID = animation.toString();

animation.setAnimationListener(new AnimationListener() {

public void onAnimationStart(Animation animation) {

//标志着正在运动

isMoving = true;

public void onAnimationRepeat(Animation animation) {

// TODO Auto-generated method stub

public void onAnimationEnd(Animation animation) {

// 假设为最后个动画结束,那运行以下的方法

if (animation.toString().equalsIgnoreCase(

LastAnimationID)) {

DragGridAdapter mDragAdapter = (DragGridAdapter)

getAdapter();

mDragAdapter.exchange(startPosition,dropPosition);

startPosition = dropPosition;

isMoving = false;

/** 获取移动动画 */

public Animation getMoveAnimation(float toXValue, float toYValue) {

TranslateAnimation mTranslateAnimation = new TranslateAnimation(

Animation.RELATIVE_TO_SELF, 0.0F, Animation.RELATIVE_TO_SELF,

toXValue, Animation.RELATIVE_TO_SELF, 0.0F,

Animation.RELATIVE_TO_SELF, toYValue);// 当前位置移动到指定位置

mTranslateAnimation.setFillAfter(true);// 设置一个动画效果运行完成后。View对象保留在终止的位置。

mTranslateAnimation.setDuration(300L);

return mTranslateAnimation;

* 注意。拖动的时候手指是不动的,手指相对于Item左上角的位置是始终不变的

* 手指位置相对于屏幕左上角是变化的,我们计算的就是变化过后手指相对于屏幕左上角的x,y值 在拖动的情况下

* 又一次绘制你手指选中的那个方法

* 手指相对于girdView左上角的横坐标的位置

* 手指相对于girdView左上角的纵坐标的位置

* @param rawx

* 手指的相对于屏幕左上角的横坐标的位置

* @param rawy

* 手指相对于屏幕左上角的纵坐标的位置

private void onDrag(int rawx, int rawy) {

if (dragImageView != null) {

// 设置窗体的透明度

windowParams.alpha = 0.6f;

// 又一次计算此时item的x和y坐标的位置

windowParams.x = rawx - itemViewX;

windowParams.y = rawy - itemViewY;

// 更新view的布局,也就是又一次绘制它的位置

windowManager.updateViewLayout(dragImageView, windowParams);

* 本方法的就是在长按某一个item的时候。生成该item的一个ImageView对象,保存在dragImageView中

* 然后用这个dragImageView在action_move中随着手势的运行不停的绘制该imageView的位置

* @param dragBitmap

* @param rawX

* @param rawY

protected void startDrag(Bitmap dragBitmap, int rawX, int rawY) {

windowParams = new WindowManager.LayoutParams();

windowParams.gravity = Gravity.TOP | Gravity.LEFT;

// 计算item左上角的坐标值

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 = new ImageView(getContext());

iv.setImageBitmap(dragBitmap);

windowManager = (WindowManager) getContext().getSystemService(

Context.WINDOW_SERVICE);// "window"

windowManager.addView(iv, windowParams);

//保存生成的imageView

dragImageView = iv;

/** 停止之前的拖动,把之前拖动的那个item从windowManage里面remove掉 **/

private void stopDrag() {

windowManager.removeView(dragImageView);

dragImageView = null;

相关Adapter的代码getView方法例如以下:

本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/5372445.html,如需转载请自行联系原作者

继续阅读