欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2023-11-04 14:36:58
写在前面的话 在实现了通过布局泵拿到不同布局为listitem布局,然后实现联系人的listview,这一章要做的是拖拽listview的item项,本章原理是在上一篇博客...
写在前面的话
在实现了通过布局泵拿到不同布局为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和案例去学习,去进步,祝成功!

源码下载