首页 > 系统 > Android > 正文

自定义ListView实现拖拽ListItem项交换位置(附源码)

2019-10-24 20:52:42
字体:
来源:转载
供稿:网友
本文要实现的是拖拽ListView的Item项,在布局方面还是用基于布局泵LayoutInflater来从不同的Layout模板拿到不同的布局然后将view返回,感兴趣的朋友可以了解下哈
 
写在前面的话 
上一篇实现了通过布局泵拿到不同布局为listitem布局,然后实现联系人的ListView,这一章要做的是拖拽ListView的Item项,本章原理是在上一篇博客基础之上的,上一篇博客:自定义Adapter并通过布局泵LayoutInflater抓取layout模板编辑每一个item 

实现效果图 
自定义ListView实现拖拽ListItem项交换位置(附源码) 

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

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

方法执行顺序 
[DragView] -> [初始化ListViewContext,和触发move事件的最小距离变量] 
[onInterceptTouchEvent] -> [初始化拖动的变量] 
[startDrag] -> [准备拖动的影像、window等变量] 
[stopDrag] -> [判断重置拖动的影像] 
[startDrag] -> [准备拖动的影像、window等变量] 
[onTouchEvent] -> [判断点击事件、根据动作做不同操作,或重新绘制Move影响,或者Stop停止拖动。交换数据,也就是下面的这两个方法] 
[onDrag] -> [实现滚动的动作] 
[onDrop] -> [实现数据item位置切换] 
注意 
上面的方法执行顺序只是大概逻辑,这其中还有判断和方法中调用其他方法,所以方法的调用是多次的,大家凑合看一下方法的大体功能吧,需要注意的是我们重写的onTouchEvent是要一直不断的监听我们的按键的,如果为Move的话也就是会一直不断的去调用onDrag方法去实现滚动的动作,在此我做了测试,给大家看一下打印的日志如下: 
自定义ListView实现拖拽ListItem项交换位置(附源码) 
我们看到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和案例去学习,去进步,祝成功! 

源码下载

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表