Android自定义ListView实现仿QQ可拖拽列表功能
程序员文章站
2024-03-06 16:29:50
我们大致的思路,其实是这样子的,也是我的设想,我们可以先去实现一个简单的listview的数据,但是他的adapter,我们可以用系统封装好的,然后传递进去一个实体类,最后...
我们大致的思路,其实是这样子的,也是我的设想,我们可以先去实现一个简单的listview的数据,但是他的adapter,我们可以用系统封装好的,然后传递进去一个实体类,最后自定义一个listview去操作,所以我们先把准备的工作做好,比如?
list_item.xml
<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <imageview android:id="@+id/iv_logo" android:layout_width="50dp" android:layout_height="50dp" android:layout_alignparentleft="true" android:layout_centerinparent="true" android:layout_marginleft="10dp"/> <textview android:id="@+id/textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerinparent="true" android:layout_marginleft="10dp" android:layout_torightof="@id/iv_logo"/> </relativelayout>
这就只有一个头像和一句话了,然后我们把实体类也给写完了
dragbean
package com.liuguilin.draglistviewsample.entity; /* * 项目名: draglistviewsample * 包名: com.liuguilin.draglistviewsample.entity * 文件名: dragbean * 创建者: lgl * 创建时间: 2016/8/29 22:49 * 描述: 实体类 */ public class dragbean { private int ivid; private string text; public dragbean() { } public dragbean(int ivid, string text) { this.ivid = ivid; this.text = text; } public int getivid() { return ivid; } public string gettext() { return text; } }
ok,其实很简单,id是图片,然后是文本,这样我们就可以来实现一个adapter了,这里我用的是arrayadapter这样能让我们插入和删除很轻松
dragadapter
package com.liuguilin.draglistviewsample.adapter; /* * 项目名: draglistviewsample * 包名: com.liuguilin.draglistviewsample.adapter * 文件名: dragadapter * 创建者: lgl * 创建时间: 2016/8/29 22:41 * 描述: 拖拽列表的数据源 */ import android.content.context; import android.view.view; import android.view.viewgroup; import android.widget.arrayadapter; import android.widget.imageview; import android.widget.textview; import com.liuguilin.draglistviewsample.r; import com.liuguilin.draglistviewsample.entity.dragbean; import java.util.list; public class dragadapter extends arrayadapter<dragbean> { /** * 构造方法 * * @param context * @param mlist */ public dragadapter(context context, list<dragbean> mlist) { super(context, 0, mlist); } /** * 实现view * * @param position * @param convertview * @param parent * @return */ @override public view getview(int position, view convertview, viewgroup parent) { view view; viewholder viewholder; if (convertview == null) { view = view.inflate(getcontext(), r.layout.list_item, null); viewholder = new viewholder(); viewholder.imageview = (imageview) view .findviewbyid(r.id.iv_logo); viewholder.textview = (textview) view.findviewbyid(r.id.textview); view.settag(viewholder); } else { view = convertview; viewholder = (viewholder) view.gettag(); } viewholder.imageview.setimageresource(getitem(position).getivid()); viewholder.textview.settext(getitem(position).gettext()); return view; } /** * 缓存 */ static class viewholder { imageview imageview; textview textview; } }
好的,其实到这里,他就是一个最普通的listview了,我们给他填充点数据
mainactivity
package com.liuguilin.draglistviewsample; import android.support.v7.app.appcompatactivity; import android.os.bundle; import com.liuguilin.draglistviewsample.adapter.dragadapter; import com.liuguilin.draglistviewsample.entity.dragbean; import com.liuguilin.draglistviewsample.view.draglistview; import java.util.arraylist; import java.util.list; public class mainactivity extends appcompatactivity { //列表 private draglistview mlistview; //数据 private list<dragbean> mlist = new arraylist<>(); //数据源 private dragadapter adapter; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); initview(); } /** * 初始化view */ private void initview() { mlistview = (draglistview) findviewbyid(r.id.mlistview); //新增數據 for (int i = 0; i < 30; i++) { dragbean bean = new dragbean(r.mipmap.ic_launcher, "刘某人程序员" + i); mlist.add(bean); } //初始化数据源 adapter = new dragadapter(this,mlist); mlistview.setadapter(adapter); } }
现在可以看看实际的效果了
现在我们可以重写我们的listview了
我们首先拦截他的事件
/** * 获取触点所在条目的位置 * 获取选中条目的图片 * 事件的拦截机制 * * @param ev * @return */ @override public boolean onintercepttouchevent(motionevent ev) { //识别动作 switch (ev.getaction()) { case motionevent.action_down: //获取触点的坐标 int x = (int) ev.getx(); int y = (int) ev.gety(); //这样就可以计算我按到哪个条目上了 mstartposition = mendposition = pointtoposition(x, y); //判断触点是否在logo的区域 viewgroup itemview = (viewgroup) getchildat(mstartposition - getfirstvisibleposition()); //记录手指在条目中的相对y坐标 dragpoint = y - itemview.gettop(); //listview在屏幕中的y坐标 dragoffset = (int) (ev.getrawy() - y); //拖动的图标 view dragger = itemview.findviewbyid(r.id.iv_logo); //判断触点是否在logo区域 if (dragger != null && x < dragger.getright() + 10) { //定义listview的滚动条目 //上 upscroll = getheight() / 3; //下 downscroll = getheight() * 2 / 3; //获取选中的图片/截图 itemview.setdrawingcacheenabled(true); //获取截图 bitmap bitmap = itemview.getdrawingcache(); //图片滚动 startdrag(bitmap, y); } break; } //还会传递事件到子view return false; }
获取到他的position之后我们直接截图,并且显示我们的window,这里做的事情就比较多了我们还要判断是否点击的是头像才去显示window
/** * 图片在y轴,也就是上下可滚动 * * @param bitmap * @param y */ private void startdrag(bitmap bitmap, int y) { //窗体仿照 wm = (windowmanager) getcontext().getsystemservice(context.window_service); //设置窗体参数 lparams = new windowmanager.layoutparams(); //设置宽高 lparams.width = windowmanager.layoutparams.wrap_content; lparams.height = windowmanager.layoutparams.wrap_content; //属性 lparams.flags = windowmanager.layoutparams.flag_not_focusable | windowmanager.layoutparams.flag_not_touchable | windowmanager.layoutparams.flag_keep_screen_on; //设置半透明 lparams.alpha = 0.8f; //设置居中 lparams.gravity = gravity.top; //设置xy lparams.x = 0; lparams.y = y-dragpoint + dragoffset; //属性 lparams.format = pixelformat.translucent; //设置动画 lparams.windowanimations = 0; //图片 dragimageview = new imageview(getcontext()); //设置截图 dragimageview.setimagebitmap(bitmap); //添加显示窗体 wm.addview(dragimageview, lparams); }
好的,我们初始化一下window,光显示还不行呢,我们还要可以滑动,怎么监听?ontouchevent的action_move事件是可以做到的
/** * 触摸事件 * * @param ev * @return */ @override public boolean ontouchevent(motionevent ev) { //错误的位置 if (dragimageview != null && mendposition != invalid_position) { //在滑动事件中控制上下滑动 switch (ev.getaction()) { case motionevent.action_move: //直接获取到y坐标进行移动 int movey = (int) ev.gety(); dodrag(movey); break; //停止拖动成像 case motionevent.action_up: int upy = (int) ev.gety(); stopdrag(); ondrag(upy); break; } } //拦截到事件 return true; }
我们在移动的时候不断的去更新他的位置
/** * 控制窗体移动 * * @param movey */ private void dodrag(int movey) { if (dragimageview != null) { lparams.y = movey - dragpoint + dragoffset; wm.updateviewlayout(dragimageview, lparams); } //判断移动到分割线 返回-1 int templine = pointtoposition(0, movey); //我们处理他 if (templine != invalid_position) { //只要你不移动到分割线 我才处理 mendposition = templine; } //拖拽时滚动、滚动速度 int scrollspeed = 0; //上滚 if (movey < upscroll) { scrollspeed = 10; //下滚 } else if (movey > downscroll) { scrollspeed = -10; } //开始滚动 if (scrollspeed != 0) { //计算条目的y坐标 int dragitemy = getchildat(mendposition - getfirstvisibleposition()).gettop(); //当前速度 int dy = dragitemy + scrollspeed; //设置 setselectionfromtop(mendposition, dy); } }
当你移动完成之后,我就可以停止你的window体了
/** * 停止的位置 */ private void stopdrag() { //直接移除窗体 if (dragimageview != null) { wm.removeview(dragimageview); dragimageview = null; } }
这样,我就直接拼接你的position达到一个拖拽的效果
/** * 最终开始成像 * * @param upy */ private void ondrag(int upy) { //分割线的处理 //判断移动到分割线 返回-1 int templine = pointtoposition(0, upy); //我们处理他 if (templine != invalid_position) { //只要你不移动到分割线 我才处理 mendposition = templine; } /** * 你在最上方就直接落在第一个最下方就直接最下面一个 */ //上边界处理 if (upy < getchildat(1).gettop()) { mendposition = 1; //下边界处理 } else if (upy > getchildat(getchildcount() - 1).gettop()) { mendposition = getadapter().getcount() - 1; } //开始更新item顺序 if (mendposition > 0 && mendposition < getadapter().getcount()) { dragadapter adapter = (dragadapter) getadapter(); //删除原来的条目 adapter.remove(adapter.getitem(mstartposition)); //更新 adapter.insert(adapter.getitem(mstartposition), mendposition); } }
全部代码贴上
draglistview
package com.liuguilin.draglistviewsample.view; /* * 项目名: draglistviewsample * 包名: com.liuguilin.draglistviewsample.view * 文件名: draglistview * 创建者: lgl * 创建时间: 2016/8/29 20:50 * 描述: 自定义高仿qq列表可拖拽的listview */ import android.content.context; import android.graphics.bitmap; import android.graphics.pixelformat; import android.util.attributeset; import android.view.gravity; import android.view.motionevent; import android.view.view; import android.view.viewgroup; import android.view.windowmanager; import android.widget.imageview; import android.widget.listview; import com.liuguilin.draglistviewsample.r; import com.liuguilin.draglistviewsample.adapter.dragadapter; public class draglistview extends listview { //按下选中的position private int mstartposition; //需要达到的position private int mendposition; //手指在条目中的相对y坐标 private int dragpoint; //listview在屏幕中的y坐标 private int dragoffset; //上 private int upscroll; //下 private int downscroll; //窗体 private windowmanager wm; //显示的截图 private imageview dragimageview; //窗体参数 private windowmanager.layoutparams lparams; //构造方法 public draglistview(context context, attributeset attrs) { super(context, attrs); } /** * 获取触点所在条目的位置 * 获取选中条目的图片 * 事件的拦截机制 * * @param ev * @return */ @override public boolean onintercepttouchevent(motionevent ev) { //识别动作 switch (ev.getaction()) { case motionevent.action_down: //获取触点的坐标 int x = (int) ev.getx(); int y = (int) ev.gety(); //这样就可以计算我按到哪个条目上了 mstartposition = mendposition = pointtoposition(x, y); //判断触点是否在logo的区域 viewgroup itemview = (viewgroup) getchildat(mstartposition - getfirstvisibleposition()); //记录手指在条目中的相对y坐标 dragpoint = y - itemview.gettop(); //listview在屏幕中的y坐标 dragoffset = (int) (ev.getrawy() - y); //拖动的图标 view dragger = itemview.findviewbyid(r.id.iv_logo); //判断触点是否在logo区域 if (dragger != null && x < dragger.getright() + 10) { //定义listview的滚动条目 //上 upscroll = getheight() / 3; //下 downscroll = getheight() * 2 / 3; //获取选中的图片/截图 itemview.setdrawingcacheenabled(true); //获取截图 bitmap bitmap = itemview.getdrawingcache(); //图片滚动 startdrag(bitmap, y); } break; } //还会传递事件到子view return false; } /** * 图片在y轴,也就是上下可滚动 * * @param bitmap * @param y */ private void startdrag(bitmap bitmap, int y) { //窗体仿照 wm = (windowmanager) getcontext().getsystemservice(context.window_service); //设置窗体参数 lparams = new windowmanager.layoutparams(); //设置宽高 lparams.width = windowmanager.layoutparams.wrap_content; lparams.height = windowmanager.layoutparams.wrap_content; //属性 lparams.flags = windowmanager.layoutparams.flag_not_focusable | windowmanager.layoutparams.flag_not_touchable | windowmanager.layoutparams.flag_keep_screen_on; //设置半透明 lparams.alpha = 0.8f; //设置居中 lparams.gravity = gravity.top; //设置xy lparams.x = 0; lparams.y = y-dragpoint + dragoffset; //属性 lparams.format = pixelformat.translucent; //设置动画 lparams.windowanimations = 0; //图片 dragimageview = new imageview(getcontext()); //设置截图 dragimageview.setimagebitmap(bitmap); //添加显示窗体 wm.addview(dragimageview, lparams); } /** * 触摸事件 * * @param ev * @return */ @override public boolean ontouchevent(motionevent ev) { //错误的位置 if (dragimageview != null && mendposition != invalid_position) { //在滑动事件中控制上下滑动 switch (ev.getaction()) { case motionevent.action_move: //直接获取到y坐标进行移动 int movey = (int) ev.gety(); dodrag(movey); break; //停止拖动成像 case motionevent.action_up: int upy = (int) ev.gety(); stopdrag(); ondrag(upy); break; } } //拦截到事件 return true; } /** * 最终开始成像 * * @param upy */ private void ondrag(int upy) { //分割线的处理 //判断移动到分割线 返回-1 int templine = pointtoposition(0, upy); //我们处理他 if (templine != invalid_position) { //只要你不移动到分割线 我才处理 mendposition = templine; } /** * 你在最上方就直接落在第一个最下方就直接最下面一个 */ //上边界处理 if (upy < getchildat(1).gettop()) { mendposition = 1; //下边界处理 } else if (upy > getchildat(getchildcount() - 1).gettop()) { mendposition = getadapter().getcount() - 1; } //开始更新item顺序 if (mendposition > 0 && mendposition < getadapter().getcount()) { dragadapter adapter = (dragadapter) getadapter(); //删除原来的条目 adapter.remove(adapter.getitem(mstartposition)); //更新 adapter.insert(adapter.getitem(mstartposition), mendposition); } } /** * 停止的位置 */ private void stopdrag() { //直接移除窗体 if (dragimageview != null) { wm.removeview(dragimageview); dragimageview = null; } } /** * 控制窗体移动 * * @param movey */ private void dodrag(int movey) { if (dragimageview != null) { lparams.y = movey - dragpoint + dragoffset; wm.updateviewlayout(dragimageview, lparams); } //判断移动到分割线 返回-1 int templine = pointtoposition(0, movey); //我们处理他 if (templine != invalid_position) { //只要你不移动到分割线 我才处理 mendposition = templine; } //拖拽时滚动、滚动速度 int scrollspeed = 0; //上滚 if (movey < upscroll) { scrollspeed = 10; //下滚 } else if (movey > downscroll) { scrollspeed = -10; } //开始滚动 if (scrollspeed != 0) { //计算条目的y坐标 int dragitemy = getchildat(mendposition - getfirstvisibleposition()).gettop(); //当前速度 int dy = dragitemy + scrollspeed; //设置 setselectionfromtop(mendposition, dy); } } }
然后我们引用
layout_main.xml
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent"> <com.liuguilin.draglistviewsample.view.draglistview android:id="@+id/mlistview" android:layout_width="match_parent" android:layout_height="match_parent"/> </linearlayout>
对了,别忘记了添加窗体的权限哦
<!--窗口权限--> <uses-permission android:name="android.permission.system_alert_window"/>
做完这一系列事情之后,就觉得这个设想其实是对的,然后我们可以尝试性的运行
以上所述是小编给大家介绍的android自定义listview实现仿qq可拖拽列表功能,希望对大家有所帮助