自定义ListView实现拖拽ListItem项交换位置
来源:互联网 发布:管家婆软件商品信息 编辑:程序博客网 时间:2024/05/18 01:50
写在前面的话
在上一篇实现了通过布局泵拿到不同布局为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@Overridepublic 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+标题栏+状态栏的YdragOffset = (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,触摸事件@Overridepublic 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;//配置一个影像ImageViewImageView imageViewForDragAni = new ImageView(getContext());imageViewForDragAni.setImageBitmap(bm);//配置该windowManagerwindowManager = (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);}//避免拖动到分割线返回-1int 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- 自定义ListView实现拖拽ListItem项交换位置
- RecycleView实现拖拽交换item位置
- ListView 拖拽Item交换位置
- listview中listitem点击实现沿曲线移动动画效果
- 实现多个div拖拽移动,交换位置功能,代码复制可运行,有交换特效
- Axure之交换位置实现
- 多线程ListView的listItem 赋值
- React-Native ListView拖拽交换Item
- React-Native ListView拖拽交换Item
- WPF中LISTITEM的拖拽
- sencha touch listItem 拖拽处理
- ListView拖拽交换 item 的实现(QQ 分组管理功能)
- (转)用android LinearLayout和RelativeLayout实现listView的listItem布局
- 可拖拽表格的swing实现(通过拖拽表格的行,实现交换行位置的交换)
- listview长按后拖动item交换位置的帮助类
- UItableView 如何实现Cell之间交换位置
- js实现两个div交换位置
- QTableWidget 实现整行拖放交换位置
- 常用软件工具
- 自定义Activity标题栏(Title bar)和窗体显示状态操作(requestWindowFeature()的应用)
- 动软软代码生成器使用(127.0.0.1)无法看到 SQLServer2008 新附加数据库的 原因 以及 解决方案
- oracle基本操作语句
- 实体店网络营销——【创业与梦想】
- 自定义ListView实现拖拽ListItem项交换位置
- int 与 byte[] 的相互转换
- cocos2d-x 一个plist创建多个帧动画
- ODBC与数据库连接时 rs.open 详解
- c#调用系统资源大集合
- Map.Entry遍历Map泛型
- NEC单片机学习感悟
- linux中likely与unlikely
- 单例模式