求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
android ListView和GridView拖拽移位具体实现及拓展
 

作者:jj120522,发布于2012-12-25,来源:CSDN

 

关于ListView拖拽移动位置,想必大家并不陌生,比较不错的软件都用到如此功能了.如:搜狐,网易,百度等,但是相比来说还是百度的用户体验较好,不偏心了,下面看几个示例:

     

首先说一下:拖拽ListView的item就不应该可以任意移动,只应该在ListView所在的范围内,而网易的你看看我都可以移动到状态栏了,虽然你做了处理,但是用户体验我个人感觉不好,在看看百度的,不仅控制了移动范围,更不错的百度的移动起来会时时的换位,看起来相当的形象,所以我认为这样相当的棒.

说明一点,我没有那么有才,我也是看别人代码,然后自己整理下.在这里就简单记载一下.

首先对touch事件的处理,从应用中,我们可以得出,在我们点击后面拖拉图标后,就会创建一个item的影像视图.并且可以移动该影像,而此时的ListView不应该有touch事件.

onInterceptTouchEvent方法.

/***
	 * touch事件拦截 
	 */
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		// 按下
		if (ev.getAction() == MotionEvent.ACTION_DOWN) {
			int x = (int) ev.getX();// 获取相对与ListView的x坐标
			int y = (int) ev.getY();// 获取相应与ListView的y坐标
			dragSrcPosition = dragPosition = pointToPosition(x, y);
			// 无效不进行处理
			if (dragPosition == AdapterView.INVALID_POSITION) {
				return super.onInterceptTouchEvent(ev);
			}

			// 获取当前位置的视图(可见状态)
			ViewGroup itemView = (ViewGroup) getChildAt(dragPosition
					- getFirstVisiblePosition());

			// 获取到的dragPoint其实就是在你点击指定item项中的高度.
			dragPoint = y - itemView.getTop();
			// 这个值是固定的:其实就是ListView这个控件与屏幕最顶部的距离(一般为标题栏+状态栏).
			dragOffset = (int) (ev.getRawY() - y);

			// 获取可拖拽的图标
			View dragger = itemView.findViewById(R.id.iv_drag_list_item_2);

			// x > dragger.getLeft() - 20这句话为了更好的触摸(-20可以省略)
			if (dragger != null && x > dragger.getLeft() - 20) {

				upScrollBounce = getHeight() / 3;// 取得向上滚动的边际,大概为该控件的1/3
				downScrollBounce = getHeight() * 2 / 3;// 取得向下滚动的边际,大概为该控件的2/3

				itemView.setDrawingCacheEnabled(true);// 开启cache.
				Bitmap bm = Bitmap.createBitmap(itemView.getDrawingCache());// 根据cache创建一个新的bitmap对象.
				startDrag(bm, y);// 初始化影像
			}
			// return false;
		}

		return super.onInterceptTouchEvent(ev);
	} 

这个方法的作用很简单:当我们摁下的如果是可拖拽的图标,那么进行初始化该Item的映像试图.

而在这里如果大家对WindowManager和WindowManager.LayoutParams不熟悉的朋友先去参考下这篇文章,要对WindowManager有一定的了解,简单的会应用.

接下来我们看onTouchEvent事件:

/**
	 * 触摸事件处理
	 */
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		// item的view不为空,且获取的dragPosition有效
		if (dragImageView != null && dragPosition != INVALID_POSITION) {
			int action = ev.getAction();
			switch (action) {
			case MotionEvent.ACTION_UP:
				int upY = (int) ev.getY();
				stopDrag();
				onDrop(upY);
				break;
			case MotionEvent.ACTION_MOVE:
				int moveY = (int) ev.getY();
				onDrag(moveY);

				break;
			case MotionEvent.ACTION_DOWN:
				break;
			default:
				break;
			}
			return true;// 取消ListView滑动.
		}

		return super.onTouchEvent(ev);
	} 

简单说明:首先在Touch中,我们要进行判断,是否点击的是拖动图标,如果是的话,那么对ACTION_MOVE and ACTION_UP相应事件进行处理,并且返回true or false.作用:取消ListView自身的Touch事件.如果不是的话,执行ListView 本身的Touch事件.

大致就介绍这么多,具体的实现,还是大家看源码吧,我注释的还算清晰,只要大家仔细看的话,一定可以掌握的,为什么这么说呢,技术只有在掌握了情况下才可以进行拓展.

对了,提醒大家要理解这三句话:

getRawX()和getRawY():获得的是相对屏幕的位置.

getX()和getY():获得的永远是相对view的触摸位置 坐标(这两个值不会超过view的长度和宽度)。

getLeft , getTop, getBottom,getRight, 这个指的是该控件相对于父控件的距离.

源码:

package com.jj.drag;

import android.content.Context;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;

import com.jj.drag.MainActivity.DragListAdapter;

/***
 * 自定义拖拽ListView
 * 
 * @author zhangjia
 * 
 */
public class DragListView extends ListView {

	private WindowManager windowManager;// windows窗口控制类
	private WindowManager.LayoutParams windowParams;// 用于控制拖拽项的显示的参数

	private int scaledTouchSlop;// 判断滑动的一个距离,scroll的时候会用到(24)

	private ImageView dragImageView;// 被拖拽的项(item),其实就是一个ImageView
	private int dragSrcPosition;// 手指拖动项原始在列表中的位置
	private int dragPosition;// 手指点击准备拖动的时候,当前拖动项在列表中的位置.

	private int dragPoint;// 在当前数据项中的位置
	private int dragOffset;// 当前视图和屏幕的距离(这里只使用了y方向上)

	private int upScrollBounce;// 拖动的时候,开始向上滚动的边界
	private int downScrollBounce;// 拖动的时候,开始向下滚动的边界

	private final static int step = 1;// ListView 滑动步伐.

	private int current_Step;// 当前步伐.

	/***
	 * 构造方法
	 * 
	 * @param context
	 * @param attrs
	 */
	public DragListView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	/***
	 * touch事件拦截
	 */
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		// 按下
		if (ev.getAction() == MotionEvent.ACTION_DOWN) {
			int x = (int) ev.getX();// 获取相对与ListView的x坐标
			int y = (int) ev.getY();// 获取相应与ListView的y坐标
			dragSrcPosition = dragPosition = pointToPosition(x, y);
			// 无效不进行处理
			if (dragPosition == AdapterView.INVALID_POSITION) {
				return super.onInterceptTouchEvent(ev);
			}

			// 获取当前位置的视图(可见状态)
			ViewGroup itemView = (ViewGroup) getChildAt(dragPosition
					- getFirstVisiblePosition());

			// 获取到的dragPoint其实就是在你点击指定item项中的高度.
			dragPoint = y - itemView.getTop();
			// 这个值是固定的:其实就是ListView这个控件与屏幕最顶部的距离(一般为标题栏+状态栏).
			dragOffset = (int) (ev.getRawY() - y);

			// 获取可拖拽的图标
			View dragger = itemView.findViewById(R.id.iv_drag_list_item_2);

			// x > dragger.getLeft() - 20这句话为了更好的触摸(-20可以省略)
			if (dragger != null && x > dragger.getLeft() - 20) {

				upScrollBounce = getHeight() / 3;// 取得向上滚动的边际,大概为该控件的1/3
				downScrollBounce = getHeight() * 2 / 3;// 取得向下滚动的边际,大概为该控件的2/3

				itemView.setDrawingCacheEnabled(true);// 开启cache.
				Bitmap bm = Bitmap.createBitmap(itemView.getDrawingCache());// 根据cache创建一个新的bitmap对象.
				startDrag(bm, y);// 初始化影像
			}
		}

		return super.onInterceptTouchEvent(ev);
	}

	/**
	 * 触摸事件处理
	 */
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		// item的view不为空,且获取的dragPosition有效
		if (dragImageView != null && dragPosition != INVALID_POSITION) {
			int action = ev.getAction();
			switch (action) {
			case MotionEvent.ACTION_UP:
				int upY = (int) ev.getY();
				stopDrag();
				onDrop(upY);
				break;
			case MotionEvent.ACTION_MOVE:
				int moveY = (int) ev.getY();
				onDrag(moveY);
				break;
			case MotionEvent.ACTION_DOWN:
				break;
			default:
				break;
			}
			return true;// 取消ListView滑动.
		}

		return super.onTouchEvent(ev);
	}

	/**
	 * 准备拖动,初始化拖动项的图像
	 * 
	 * @param bm
	 * @param y
	 */
	private void startDrag(Bitmap bm, int y) {
		// stopDrag();
		/***
		 * 初始化window.
		 */
		windowParams = new WindowManager.LayoutParams();
		windowParams.gravity = Gravity.TOP;
		windowParams.x = 0;
		windowParams.y = y - dragPoint + dragOffset;
		windowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
		windowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;

		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;// 窗口所使用的动画设置

		ImageView imageView = new ImageView(getContext());
		imageView.setImageBitmap(bm);
		windowManager = (WindowManager) getContext().getSystemService("window");
		windowManager.addView(imageView, windowParams);
		dragImageView = imageView;

	}

	/**
	 * 拖动执行,在Move方法中执行
	 * 
	 * @param y
	 */
	public void onDrag(int y) {
		int drag_top = y - dragPoint;// 拖拽view的top值不能<0,否则则出界.
		if (dragImageView != null && drag_top >= 0) {
			windowParams.alpha = 0.5f;// 透明度
			windowParams.y = y - dragPoint + dragOffset;// 移动y值.
           //记得要加上dragOffset,windowManager计算的是整个屏幕.(标题栏和状态栏都要算上)
			windowManager.updateViewLayout(dragImageView, windowParams);// 时时移动.
		}
		// 为了避免滑动到分割线的时候,返回-1的问题
		int tempPosition = pointToPosition(0, y);
		if (tempPosition != INVALID_POSITION) {
			dragPosition = tempPosition;

		}
		doScroller(y);
	}

	/***
	 * ListView的移动.
	 * 要明白移动原理:当映像移动到下端的时候,ListView向上滑动,当映像移动到上端的时候,ListView要向下滑动。
正好和实际的相反.
	 * 
	 */

	public void doScroller(int y) {
		Log.e("jj", "y=" + y);
		Log.e("jj", "upScrollBounce=" + upScrollBounce);
		// ListView需要下滑
		if (y < upScrollBounce) {
			current_Step = step + (upScrollBounce - y) / 10;// 时时步伐
		}// ListView需要上滑
		else if (y > downScrollBounce) {
			current_Step = -(step + (y - downScrollBounce)) / 10;// 时时步伐
		} else {
			current_Step = 0;
		}

		// 获取你拖拽滑动到位置及显示item相应的view上(注:可显示部分)(position)
		View view = getChildAt(dragPosition - getFirstVisiblePosition());
		// 真正滚动的方法setSelectionFromTop()
		setSelectionFromTop(dragPosition, view.getTop() + current_Step);

	}

	/**
	 * 停止拖动,删除影像
	 */
	public void stopDrag() {
		if (dragImageView != null) {
			windowManager.removeView(dragImageView);
			dragImageView = null;
		}
	}

	/**
	 * 拖动放下的时候
	 * 
	 * @param y
	 */
	public void onDrop(int y) {

		// 为了避免滑动到分割线的时候,返回-1的问题
		int tempPosition = pointToPosition(0, y);
		if (tempPosition != INVALID_POSITION) {
			dragPosition = tempPosition;
		}

		// 超出边界处理(如果向上超过第二项Top的话,那么就放置在第一个位置)
		if (y < getChildAt(0).getTop()) {
			// 超出上边界
			dragPosition = 0;
			// 如果拖动超过最后一项的最下边那么就防止在最下边
		} else if (y > getChildAt(getChildCount() - 1).getBottom()) {
			// 超出下边界
			dragPosition = getAdapter().getCount() - 1;
		}

		// 数据交换
		if (dragPosition < getAdapter().getCount()) {
			DragListAdapter adapter = (DragListAdapter) getAdapter();
			adapter.update(dragSrcPosition, dragPosition);
		}

	}

} 

下面我说下适配器:

/***
	 * 自定义适配器
	 * 
	 * @author zhangjia
	 * 
	 */
	class DragListAdapter extends BaseAdapter {
		private ArrayList arrayTitles;
		private ArrayList arrayDrawables;
		private Context context;

		public DragListAdapter(Context context, ArrayList arrayTitles,
				ArrayList arrayDrawables) {
			this.context = context;
			this.arrayTitles = arrayTitles;
			this.arrayDrawables = arrayDrawables;
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			View view = convertView;
			/***
			 * 在这里尽可能每次都进行实例化新的,这样在拖拽ListView的时候不会出现错乱.
			 * 具体原因不明,不过这样经过测试,目前没有发现错乱。虽说效率不高,但是做拖拽LisView足够了。
			 */
			view = LayoutInflater.from(context).inflate(
					R.layout.drag_list_item, null);

			TextView textView = (TextView) view
					.findViewById(R.id.tv_drag_list_item_text);
			ImageView imageView = (ImageView) view
					.findViewById(R.id.iv_drag_list_item_1);
			imageView.setImageResource(arrayDrawables.get(position));
			textView.setText(arrayTitles.get(position));
			return view;
		}

		/***
		 * 动态修改ListVIiw的方位.
		 * 
		 * @param start
		 *            点击移动的position
		 * @param down
		 *            松开时候的position
		 */
		public void update(int start, int down) {
			// 获取删除的东东.
			String title = arrayTitles.get(start);
			int drawable_id = arrayDrawables.get(start);

			arrayTitles.remove(start);// 删除该项
			arrayDrawables.remove(start);// 删除该项

			arrayTitles.add(down, title);// 添加删除项
			arrayDrawables.add(down, drawable_id);// 添加删除项

			notifyDataSetChanged();// 刷新ListView
		}

		@Override
		public int getCount() {

			return Title.length;
		}

		@Override
		public Object getItem(int position) {
			return Title[position];
		}

		@Override
		public long getItemId(int position) {
			return position;
		}

	} 

这里不过多解释了,相信大家都看的明白.如果疑问请留言.

展示下运行效果:

效果看起来还行吧,如果觉得不错的话,记得要赞一个哦.

下面我们接着修改,模拟百度嘛,谁让百度这么牛叉呢.

思路:点中拖拉图标的时候,每次移动只要dragPosition发生改变,也就是我移动到了下一个位置,那么此时我就进行交换执行update.并且除了第一次移动外,在每次交换后要除去映射源的显示,这样用户觉得这里的空位就是就是为我准备的,比较人性化.

实现起来并不复杂,前提是你得掌握上面的操作.

源码如下;

package com.jj.drag;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;

import com.jj.drag.MainActivity.DragListAdapter;

public class DragListView extends ListView {

	private WindowManager windowManager;// windows窗口控制类
	private WindowManager.LayoutParams windowParams;// 用于控制拖拽项的显示的参数

	private int scaledTouchSlop;// 判断滑动的一个距离,scroll的时候会用到(24)

	private ImageView dragImageView;// 被拖拽的项(item),其实就是一个ImageView
	private int dragSrcPosition;// 手指拖动项原始在列表中的位置
	private int dragPosition;// 手指点击准备拖动的时候,当前拖动项在列表中的位置.

	private int dragPoint;// 在当前数据项中的位置
	private int dragOffset;// 当前视图和屏幕的距离(这里只使用了y方向上)

	private int upScrollBounce;// 拖动的时候,开始向上滚动的边界
	private int downScrollBounce;// 拖动的时候,开始向下滚动的边界

	private final static int step = 1;// ListView 滑动步伐.

	private int current_Step;// 当前步伐.

	private int temChangId;// 临时交换id

	private boolean isLock;// 是否上锁.

	public void setLock(boolean isLock) {
		this.isLock = isLock;
	}

	public DragListView(Context context, AttributeSet attrs) {
		super(context, attrs);
		scaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
	}

	/***
	 * touch事件拦截 在这里我进行相应拦截,
	 */
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		// 按下
		if (ev.getAction() == MotionEvent.ACTION_DOWN && !isLock) {
			int x = (int) ev.getX();// 获取相对与ListView的x坐标
			int y = (int) ev.getY();// 获取相应与ListView的y坐标
			temChangId = dragSrcPosition = dragPosition = pointToPosition(x, y);
			// 无效不进行处理
			if (dragPosition == AdapterView.INVALID_POSITION) {
				return super.onInterceptTouchEvent(ev);
			}

			// 获取当前位置的视图(可见状态)
			ViewGroup itemView = (ViewGroup) getChildAt(dragPosition
					- getFirstVisiblePosition());

			// 获取到的dragPoint其实就是在你点击指定item项中的高度.
			dragPoint = y - itemView.getTop();
			// 这个值是固定的:其实就是ListView这个控件与屏幕最顶部的距离(一般为标题栏+状态栏).
			dragOffset = (int) (ev.getRawY() - y);

			// 获取可拖拽的图标
			View dragger = itemView.findViewById(R.id.iv_drag_list_item_2);

			// x > dragger.getLeft() - 20这句话为了更好的触摸(-20可以省略)
			if (dragger != null && x > dragger.getLeft() - 20) {

				upScrollBounce = getHeight() / 3;// 取得向上滚动的边际,大概为该控件的1/3
				downScrollBounce = getHeight() * 2 / 3;// 取得向下滚动的边际,大概为该控件的2/3
				itemView.setBackgroundColor(Color.BLUE);
				itemView.setDrawingCacheEnabled(true);// 开启cache.
				Bitmap bm = Bitmap.createBitmap(itemView.getDrawingCache());// 根据cache创建一个新的bitmap对象.
				startDrag(bm, y);// 初始化影像
			}
			return false;
		}

		return super.onInterceptTouchEvent(ev);
	}

	/**
	 * 触摸事件处理
	 */
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		// item的view不为空,且获取的dragPosition有效
		if (dragImageView != null && dragPosition != INVALID_POSITION
				&& !isLock) {
			int action = ev.getAction();
			switch (action) {
			case MotionEvent.ACTION_UP:
				int upY = (int) ev.getY();
				stopDrag();
				onDrop(upY);
				break;
			case MotionEvent.ACTION_MOVE:
				int moveY = (int) ev.getY();
				onDrag(moveY);

				break;
			case MotionEvent.ACTION_DOWN:
				break;
			default:
				break;
			}
			return true;// 取消ListView滑动.
		}

		return super.onTouchEvent(ev);
	}

	/**
	 * 准备拖动,初始化拖动项的图像
	 * 
	 * @param bm
	 * @param y
	 */
	private void startDrag(Bitmap bm, int y) {
		// stopDrag();
		/***
		 * 初始化window.
		 */
		windowParams = new WindowManager.LayoutParams();
		windowParams.gravity = Gravity.TOP;
		windowParams.x = 0;
		windowParams.y = y - dragPoint + dragOffset;
		windowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
		windowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;

		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;// 窗口所使用的动画设置

		ImageView imageView = new ImageView(getContext());
		imageView.setImageBitmap(bm);
		windowManager = (WindowManager) getContext().getSystemService("window");
		windowManager.addView(imageView, windowParams);
		dragImageView = imageView;

	}

	/**
	 * 拖动执行,在Move方法中执行
	 * 
	 * @param y
	 */
	public void onDrag(int y) {
		int drag_top = y - dragPoint;// 拖拽view的top值不能<0,否则则出界.
		if (dragImageView != null && drag_top >= 0) {
			windowParams.alpha = 0.5f;
			windowParams.y = y - dragPoint + dragOffset;
			windowManager.updateViewLayout(dragImageView, windowParams);// 时时移动.
		}
		// 为了避免滑动到分割线的时候,返回-1的问题
		int tempPosition = pointToPosition(0, y);
		if (tempPosition != INVALID_POSITION) {
			dragPosition = tempPosition;
		}

		onChange(y);// 时时交换

		doScroller(y);// listview移动.
	}

	/***
	 * ListView的移动.
	 * 要明白移动原理:当我移动到下端的时候,ListView向上滑动,当我移动到上端的时候,ListView要向下滑动。
正好和实际的相反.
	 * 
	 */

	public void doScroller(int y) {
		// Log.e("jj", "y=" + y);
		// Log.e("jj", "upScrollBounce=" + upScrollBounce);
		// ListView需要下滑
		if (y < upScrollBounce) {
			current_Step = step + (upScrollBounce - y) / 10;// 时时步伐
		}// ListView需要上滑
		else if (y > downScrollBounce) {
			current_Step = -(step + (y - downScrollBounce)) / 10;// 时时步伐
		} else {
			current_Step = 0;
		}

		// 获取你拖拽滑动到位置及显示item相应的view上(注:可显示部分)(position)
		View view = getChildAt(dragPosition - getFirstVisiblePosition());
		// 真正滚动的方法setSelectionFromTop()
		setSelectionFromTop(dragPosition, view.getTop() + current_Step);

	}

	/**
	 * 停止拖动,删除影像
	 */
	public void stopDrag() {
		if (dragImageView != null) {
			windowManager.removeView(dragImageView);
			dragImageView = null;
		}
	}

	/***
	 * 拖动时时change
	 */
	private void onChange(int y) {
		// 数据交换
		if (dragPosition < getAdapter().getCount()) {
			DragListAdapter adapter = (DragListAdapter) getAdapter();
			adapter.isHidden = false;
			if (dragPosition != temChangId) {
				adapter.update(temChangId, dragPosition);
				temChangId = dragPosition;// 将点击最初所在位置position付给临时的,用于判断是否换位.
			}
		}

		// 为了避免滑动到分割线的时候,返回-1的问题
		int tempPosition = pointToPosition(0, y);
		if (tempPosition != INVALID_POSITION) {
			dragPosition = tempPosition;
		}

		// 超出边界处理(如果向上超过第二项Top的话,那么就放置在第一个位置)
		if (y < getChildAt(0).getTop()) {
			// 超出上边界
			dragPosition = 0;
			// 如果拖动超过最后一项的最下边那么就防止在最下边
		} else if (y > getChildAt(getChildCount() - 1).getBottom()) {
			// 超出下边界
			dragPosition = getAdapter().getCount() - 1;
		}

	}

	/**
	 * 拖动放下的时候
	 * 
	 * @param y
	 */
	public void onDrop(int y) {
		// 数据交换
		if (dragPosition < getAdapter().getCount()) {
			DragListAdapter adapter = (DragListAdapter) getAdapter();
			adapter.isHidden = false;
			adapter.notifyDataSetChanged();// 刷新.
		}
	}

} 

因为我们要时时交换位置,所以将原先的拖动方法onDrop方法移动到onChange中.具体的还是看源码吧.

另外的就是对适配器的修改,因为你要对特殊的item进行隐藏之类的操作,这些代码我就不写了,我会将案例上传网上,不懂的可以下载源码.

好了还是我们来观看下效果吧.

怎么样,这个效果看起来要比上面那个效果更人性化点吧,我的操作或许有点快,不信的话,你自己手机体验一下吧.

关于ListView拖拽就说到这里,如有不足请大家自己创新.

下面我们接着对GridView的拖拽简单说明.因为这些在项目中我们都会用到,所以既然做到就做全面点吧.好了大家接着往下看吧.

首先说明,原理一样,都是拖动映像,记录拖动位置,然后调用notifyDataSetChanged更新UI.

而GridView不同的是你要根据x,y值共同获取点击的position和移动至的position,而ListView因为不涉及x坐标.

嗯,最初的原始移动我就不给大家展示了,效果也不是很友好,我直接展示时时更新的那种方法.效果类是与上面那个时时更新ListView一样。

原理也一样.下面我们直接看代码吧.

package com.jj.draggrid;

import java.util.logging.Handler;

import com.jj.draggrid.MainActivity.DragGridAdapter;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.Toast;

/***
 * 自定义拖拽GridView
 * 
 * @author zhangjia
 * 
 */

public class DragGridView extends GridView {

	private WindowManager windowManager;// windows窗口控制类
	private WindowManager.LayoutParams windowParams;// 用于控制拖拽项的显示的参数

	private int scaledTouchSlop;// 判断滑动的一个距离,scroll的时候会用到(24)

	private ImageView dragImageView;// 被拖拽的项(item),其实就是一个ImageView
	private int dragSrcPosition;// 手指拖动项原始在列表中的位置
	private int dragPosition;// 手指点击准备拖动的时候,当前拖动项在列表中的位置.

	private int dragPointX;// 在当前数据项中的位置
	private int dragPointY;// 在当前数据项中的位置
	private int dragOffsetX;// 当前视图和屏幕的距离(这里只使用了x方向上)
	private int dragOffsetY;// 当前视图和屏幕的距离(这里只使用了y方向上)

	private int upScrollBounce;// 拖动的时候,开始向上滚动的边界
	private int downScrollBounce;// 拖动的时候,开始向下滚动的边界

	private int temChangId;// 临时交换id

	private boolean isDoTouch = false;// touch是否可用

	private boolean isHide = false;// 是否隐藏

	private Handler handler;

	public void setDoTouch(boolean isDoTouch) {
		this.isDoTouch = isDoTouch;
	}

	public DragGridView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {

		if (ev.getAction() == MotionEvent.ACTION_DOWN) {
			int x = (int) ev.getX();
			int y = (int) ev.getY();

			temChangId = dragSrcPosition = dragPosition = pointToPosition(x, y);

			if (dragPosition == AdapterView.INVALID_POSITION) {
				return super.onInterceptTouchEvent(ev);
			}

			ViewGroup itemView = (ViewGroup) getChildAt(dragPosition
					- getFirstVisiblePosition());

			dragPointX = x - itemView.getLeft();
			dragPointY = y - itemView.getTop();
			dragOffsetX = (int) (ev.getRawX() - x);
			dragOffsetY = (int) (ev.getRawY() - y);

			View dragger = itemView.findViewById(R.id.drag_grid_item);

			/***
			 * 判断是否选中拖动图标
			 */
			if (dragger != null && dragPointX > dragger.getLeft()
					&& dragPointX < dragger.getRight()
					&& dragPointY > dragger.getTop()
					&& dragPointY < dragger.getBottom() + 20) {

				upScrollBounce = getHeight() / 4;
				downScrollBounce = getHeight() * 3 / 4;

				itemView.setDrawingCacheEnabled(true);
				Bitmap bm = Bitmap.createBitmap(itemView.getDrawingCache());
				startDrag(bm, x, y);// 初始话映像

				dragger.setVisibility(View.INVISIBLE);// 隐藏该项.
			}
		}

		return super.onInterceptTouchEvent(ev);
	}

	@Override
	public boolean onTouchEvent(MotionEvent ev) {

		if (dragImageView != null && dragPosition != INVALID_POSITION
				&& isDoTouch) {
			int action = ev.getAction();
			switch (action) {
			/***
			 * 
			 */
			case MotionEvent.ACTION_UP:
				int upX = (int) ev.getX();
				int upY = (int) ev.getY();
				stopDrag();// 删除映像
				onDrop(upX, upY);// 松开
				// isDoTouch = false;
				break;
			/***
			 * 拖拽item
			 * 
			 */
			case MotionEvent.ACTION_MOVE:
				int moveX = (int) ev.getX();
				int moveY = (int) ev.getY();
				onDrag(moveX, moveY);// 拖拽
				break;
			case MotionEvent.ACTION_DOWN:
				int downX = (int) ev.getX();
				int downY = (int) ev.getY();
				onHide(downX, downY);// 隐藏该项
				break;
			default:
				break;
			}
			return true;
		}

		return super.onTouchEvent(ev);
	}

	/**
	 * 准备拖动,初始化拖动项的图像
	 * 
	 * @param bm
	 * @param y
	 */
	public void startDrag(Bitmap bm, int x, int y) {

		windowParams = new WindowManager.LayoutParams();
		windowParams.gravity = Gravity.TOP | Gravity.LEFT;
		windowParams.x = x - dragPointX + dragOffsetX;
		windowParams.y = y - dragPointY + dragOffsetY;
		windowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
		windowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
		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.windowAnimations = 0;

		ImageView imageView = new ImageView(getContext());
		imageView.setImageBitmap(bm);
		windowManager = (WindowManager) getContext().getSystemService("window");
		windowManager.addView(imageView, windowParams);
		dragImageView = imageView;

	}

	/***
	 * 拖动时时change
	 */
	private void onChange(int x, int y) {
		// 获取适配器
		DragGridAdapter adapter = (DragGridAdapter) getAdapter();
		// 数据交换
		if (dragPosition < getAdapter().getCount()) {
			// 不相等的情况下要进行换位,相等的情况下说明正在移动
			if (dragPosition != temChangId) {
				adapter.update(temChangId, dragPosition);// 进行换位
				temChangId = dragPosition;// 将点击最初所在位置position付给临时的,用于判断是否换位.
			}

		}

		// 为了避免滑动到分割线的时候,返回-1的问题
		int tempPosition = pointToPosition(x, y);
		if (tempPosition != INVALID_POSITION) {
			dragPosition = tempPosition;
		}

	}

	/***
	 * 拖动执行,在Move方法中执行
	 * 
	 * @param x
	 * @param y
	 */
	public void onDrag(int x, int y) {
		// 移动
		if (dragImageView != null) {
			windowParams.alpha = 0.8f;
			windowParams.x = x - dragPointX + dragOffsetX;
			windowParams.y = y - dragPointY + dragOffsetY;
			windowManager.updateViewLayout(dragImageView, windowParams);
		}

		onChange(x, y);// 时时交换

		// 滚动
		if (y < upScrollBounce || y > downScrollBounce) {
			// 使用setSelection来实现滚动
			setSelection(dragPosition);
		}

	}

	/***
	 * 隐藏该选项
	 */
	private void onHide(int x, int y) {
		// 获取适配器
		DragGridAdapter adapter = (DragGridAdapter) getAdapter();
		// 为了避免滑动到分割线的时候,返回-1的问题
		int tempPosition = pointToPosition(x, y);
		if (tempPosition != INVALID_POSITION) {
			dragPosition = tempPosition;
		}
		adapter.setIsHidePosition(dragPosition);

	}

	/**
	 * 停止拖动,删除影像
	 */
	public void stopDrag() {
		if (dragImageView != null) {
			windowManager.removeView(dragImageView);
			dragImageView = null;
		}
	}

	/***
	 * 拖动放下的时候
	 * 
	 * @param x
	 * @param y
	 */
	public void onDrop(int x, int y) {

		DragGridAdapter adapter = (DragGridAdapter) getAdapter();
		adapter.setIsHidePosition(-1);// 不进行隐藏

	}

} 

相信大家只要ListView拖拽弄白后,这个GridView也会轻易弄出来,其实拖拽就是对坐标的考察。

向大家展示一下效果:

但是有个不足的地方,网上一些例子都是长按可以拖拽,而点击则执行点击事件.其实实现起来也不是很复杂,可是在实现的过程中,遇到了诡异纠结的问题,郁闷了一天,结果目前先放弃,以后哪天在搞搞吧.纠结的问题就是错位.

我说下我的思路:首先,我们在自定义GridView中创建一个控制是否可以Touch拖拽的变量,而这个变量的值我们通过对GridView的setOnItemClickListener和setOnItemLongClickListener来获取,如:

gv_main.setOnItemClickListener(new OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView<?> parent, View view,
					int position, long id) {
				gv_main.setDoTouch(false);
				Toast.makeText(MainActivity.this,
						adapter.getItemId(position) + "", 1).show();
			}
		});

		gv_main.setOnItemLongClickListener(new OnItemLongClickListener() {

			@Override
			public boolean onItemLongClick(AdapterView<?> parent, View view,
					int position, long id) {
				gv_main.setDoTouch(true);
				return true;
			}
		}); 

这样我们就实现了长按可以拖拽的效果了,可是遇到个变态的问题,不过这个思路没有错,肯定可以实现.

就先说到这里,其实通过这个例子,我们还可以拓展实现ListView上滑动的时候,到达Title时,Title停留在顶部,当下一个Titile滑动到这里的时候,那么代替前面那个TItle.网上有写应该就是这么搞的,具体实现不知道,不过这种方案可以实现,有时间接着续.


 
分享到
 
 


android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   


Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合


领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...