您的位置:首页 > 其它

自定义ListView实现拖拽ListItem项交换位置

2013-04-24 13:27 399 查看
写在前面的话

在上一篇实现了通过布局泵拿到不同布局为listitem布局,然后实现联系人的ListView,这一章要做的是拖拽ListView的Item项,本章原理是在上一篇博客基础之上的,上一篇博客:自定义Adapter并通过布局泵LayoutInflater抓取layout模板编辑每一个item

实现效果图



说明

首先我们看到的上面这张图就是实现的效果图了。拖动之后数据项完成交换位置。



功能剖析

我们看到做的这个效果是一个拖拽ListView的Item项位置的功能,在布局方面还是用基于布局泵LayoutInflater来从不同的Layout模板拿到不同的布局然后将view返回。关于布局这一点的知识在上一篇有详细说明,文章开头已经说明,OK,下面我们来剖析一下这个拖动效果的实现吧,下面的文章将会以方法执行的顺序来给出各个方法的代码。然后依次剖析每个方法的作用。



方法执行顺序

[DragView] -> [初始化ListViewContext,和触发move事件的最小距离变量]

[onInterceptTouchEvent] -> [初始化拖动的变量]

[startDrag] -> [准备拖动的影像、window等变量]

[stopDrag] -> [判断重置拖动的影像]

[startDrag] -> [准备拖动的影像、window等变量]

[onTouchEvent] -> [判断点击事件、根据动作做不同操作,或重新绘制Move影响,或者Stop停止拖动。交换数据,也就是下面的这两个方法]

[onDrag] -> [实现滚动的动作]

[onDrop] -> [实现数据item位置切换]





注意

上面的方法执行顺序只是大概逻辑,这其中还有判断和方法中调用其他方法,所以方法的调用是多次的,大家凑合看一下方法的大体功能吧,需要注意的是我们重写的onTouchEvent是要一直不断的监听我们的按键的,如果为Move的话也就是会一直不断的去调用onDrag方法去实现滚动的动作,在此我做了测试,给大家看一下打印的日志如下:



我们看到move日志被执行了多次。说明我们在拖动的时候一直在执行这个方法。下面我会给大家自定义ListView的代码,代码中注释量很大,可读性很高,推荐大家参考上面我给出的方法运行顺序去理解,最后我将给出运行源码。

package com.example.draglistview;

import com.example.draglistview.MainActivity.DragListAdapter;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
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.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Toast;

public class DragView extends ListView{
	private ImageView imageView;	//被拖动的图片
	private int scaledTouchSlop;	//判断拖动的距离
	
	private int dragSrcPosition;	//手指在touch事件触摸时候的原始位置 
	private int dragPosition;		//手指拖动列表项时候的位置
	
	private int dragPoint;		//手指点击位置在当前数据项item中的位置,只有Y坐标
	private int dragOffset;		//当前视图listview在屏幕中的位置,只有Y坐标
	
	private int upScrollBounce;	//向上滑动的边界
	private int downScrollBounce;	//拖动的时候向下滑动的边界
	
	private WindowManager windowManager = null;	//窗口管理类
	//窗口参数类
	private WindowManager.LayoutParams layoutParams = null;
	
	
	
	//注意该View如果在Layout xml 注册使用的话必须使用下面的这个构造进行初始化
	public DragView(Context context, AttributeSet attrs) {
		super(context, attrs);
		//触发移动事件的最小距离
		scaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
	}

	//重写于absListView
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		
		if(ev.getAction() == MotionEvent.ACTION_DOWN){
			//获取的该touch事件的x坐标和y坐标,该坐标是相对于组件的左上角的位置
			int x = (int) ev.getX();
			int y = (int) ev.getY();
			//赋值手指点击时候的开始坐标
			dragSrcPosition = dragPosition = this.pointToPosition(x, y);
			//如果点击在列表之外,也就是不允许的位置
			if(dragPosition == AdapterView.INVALID_POSITION){
				//直接执行父类,不做任何操作
				return super.onInterceptTouchEvent(ev);
			}
			
			/***
			 * 锁定手指touch的列表item,
			 * 参数为屏幕的touch坐标减去listview左上角的坐标
			 * 这里的getChildAt方法参数为相对于组件左上角坐标为00的情况
			 * 故有下面的这种参数算法
			 */
			ViewGroup itemView = (ViewGroup) this.getChildAt(dragPosition-this.getFirstVisiblePosition());
			/****
			 * 说明:getX Y为touch点相对于组件左上角的距离
			 * getRawX 、Y 为touch点相对于屏幕左上角的距离
			 * 参考http://blog.csdn.net/love_world_/article/details/8164293
			 */
			//touch点的view相对于该childitem的top坐标的距离
			dragPoint = y-itemView.getTop();
			//为距离屏幕左上角的Y减去距离组件左上角的Y,其实就是
			//组件上方的view+标题栏+状态栏的Y
			dragOffset = (int) (ev.getRawY()-y);
			
			//拿到拖动的imageview对象
			View drager = itemView.findViewById(R.id.imageView1);
			
			//判断条件为拖动touch图片是否为null和touch的位置,是否符合
			if(drager != null && x>drager.getLeft()-20){
				
				//判断得出向上滑动和向下滑动的值
				upScrollBounce = Math.min(y-scaledTouchSlop, getHeight()/3);
				downScrollBounce = Math.max(y+scaledTouchSlop, getHeight()*2/3);
				//启用绘图缓存
				itemView.setDrawingCacheEnabled(true);
				//根据图像缓存拿到对应位图
				Bitmap bm = Bitmap.createBitmap(itemView.getDrawingCache());
				startDrag(bm, y);
			}
			return false;
		}
		return super.onInterceptTouchEvent(ev);
	}

	
	
	//重写OnTouchEvent,触摸事件
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if(imageView != null && dragPosition != INVALID_POSITION){
			int currentAction = ev.getAction();
			
			switch (currentAction) {
			case MotionEvent.ACTION_UP:
				int upY = (int) ev.getY();
				//还有一些操作
				stopDrag();
                onDrop(upY);
				break;
			case MotionEvent.ACTION_MOVE:	
				Log.v("move", "move---------");
				int moveY = (int) ev.getY();
				onDrag(moveY);
				break;
			default:
				break;
			}
			return true;
		}
		//决定了选中的效果
		return super.onTouchEvent(ev);
	}
	
	
	
	/****
	 * 准备拖动,初始化拖动时的影像,和一些window参数
	 * @param bm	拖动缓存位图
	 * @param y		拖动之前touch的位置
	 */
	public void startDrag(Bitmap bm,int y){
		stopDrag();
		layoutParams = new WindowManager.LayoutParams();
		//设置重力
		layoutParams.gravity = Gravity.TOP;
		//横轴坐标不变
		layoutParams.x = 0;
		/**
		 * 
		 * y轴坐标为   视图相对于自身左上角的Y-touch点在列表项中的y
		 * +视图相对于屏幕左上角的Y,=
		 * 该view相对于屏幕左上角的位置
		 */
		layoutParams.y = y-dragPoint+dragOffset;
		/****
		 * 宽度和高度都为wrapContent
		 */
		layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
		layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
		
		/****
		 * 设置该layout参数的一些flags参数
		 */
		layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
		//设置该window项是半透明的格式
		layoutParams.format = PixelFormat.TRANSLUCENT;
		//设置没有动画
		layoutParams.windowAnimations = 0;
		
		//配置一个影像ImageView
		ImageView imageViewForDragAni = new ImageView(getContext());
		imageViewForDragAni.setImageBitmap(bm);
		//配置该windowManager
		windowManager = (WindowManager) this.getContext().getSystemService("window");
		windowManager.addView(imageViewForDragAni, layoutParams);
		imageView = imageViewForDragAni;
	}
	
	/***
	 * 停止拖动,去掉拖动时候的影像
	 */
	public void stopDrag(){
		if(imageView != null){
			windowManager.removeView(imageView);
			imageView = null;
		}
	}
	
	
	/****
	 * 拖动方法
	 * @param y
	 */
	public void onDrag(int y){
		
		if(imageView != null){
			//透明度
			layoutParams.alpha = 0.8f;
			layoutParams.y = y-this.dragPoint+this.dragOffset;
			windowManager.updateViewLayout(imageView, layoutParams);
		}
		
		
		//避免拖动到分割线返回-1
		int tempPosition = this.pointToPosition(0, y);
		if(tempPosition != this.INVALID_POSITION){
			this.dragPosition = tempPosition;
		}
		
		
        int scrollHeight = 0;
        if(y<upScrollBounce){
            scrollHeight = 8;//定义向上滚动8个像素,如果可以向上滚动的话
        }else if(y>downScrollBounce){
            scrollHeight = -8;//定义向下滚动8个像素,,如果可以向上滚动的话
        }
		
        if(scrollHeight!=0){
            //真正滚动的方法setSelectionFromTop()
            setSelectionFromTop(dragPosition, getChildAt(dragPosition-getFirstVisiblePosition()).getTop()+scrollHeight);
        }
	}
	
	
	/***
	 * 拖动放下的时候
	 * param : y
	 */
	public void onDrop(int y){
		int tempPosition = this.pointToPosition(0, y);
		if(tempPosition != this.INVALID_POSITION){
			this.dragPosition = tempPosition;
		}
		
        //超出边界处理
        if(y<getChildAt(1).getTop()){
            //超出上边界
            dragPosition = 1;
        }else if(y>getChildAt(getChildCount()-1).getBottom()){
            //超出下边界
            dragPosition = getAdapter().getCount()-1;
            //
        }
        
        
        
       //数据交换
        if(dragPosition>0&&dragPosition<getAdapter().getCount()){
            @SuppressWarnings("unchecked")
            DragListAdapter adapter = (DragListAdapter)getAdapter();
            //原始位置的item
            String dragItem = adapter.getItem(dragSrcPosition);
            adapter.remove(dragItem);
            adapter.insert(dragItem, dragPosition);
            Toast.makeText(getContext(), adapter.getList().toString(), Toast.LENGTH_SHORT).show();
        }
	}
}


写在后面的话

以上就为自定义ListView的源码了。当然还有部分未给出的代码。包括MainActivity、3个Layout xml 文件。[比较简单] 如果你看懂了[或者有一些疑问]上面的这部分代码,你可以下载我的源码查相关的API和案例去学习,去进步,祝成功!



源码下载地址

http://pan.baidu.com/share/link?shareid=465193&uk=1997312776
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐