自定义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的代码,代码中注释量很大,可读性很高,推荐大家参考上面我给出的方法运行顺序去理解,最后我将给出运行源码。
写在后面的话
以上就为自定义ListView的源码了。当然还有部分未给出的代码。包括MainActivity、3个Layout xml 文件。[比较简单] 如果你看懂了[或者有一些疑问]上面的这部分代码,你可以下载我的源码查相关的API和案例去学习,去进步,祝成功!
源码下载地址
http://pan.baidu.com/share/link?shareid=465193&uk=1997312776
在上一篇实现了通过布局泵拿到不同布局为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
相关文章推荐
- 自定义ListView实现拖拽ListItem项交换位置(附源码)
- ListView通过自定义适配器来显示数据并对Item项以及子view项的控件实现监听.
- Android高级控件(六)——自定义ListView高仿一个QQ可拖拽列表的实现
- android 学习笔记:自定义通用ListView/GridView,实现ListAdapter 类
- 实现多个div拖拽移动,交换位置功能,代码复制可运行,有交换特效
- Android使用RecycleView实现拖拽交换item位置
- Android高级控件(六)——自定义ListView高仿一个QQ可拖拽列表的实现
- Android使用RecycleView实现拖拽交换item位置
- ListView通过自定义适配器来显示数据并对Item项以及子view项的控件实现监听
- ListView拖拽交换 item 的实现(QQ 分组管理功能)
- ListView通过自定义适配器来显示数据并对Item项以及子view项的控件实现监听.
- ListView实现Item上下拖动交换位置 并且实现下拉刷新 上拉加载更多
- 使用自定义适配器实现ListView中的每一个list的不同显示风格
- 原生js实现照片墙的效果(拖拽图片与另一张图片交换位置)
- Android自定义ListView实现仿QQ可拖拽列表功能
- 可拖拽表格的swing实现(通过拖拽表格的行,实现交换行位置的交换)
- Android 可拖拽的GridView效果实现, 长按可拖拽和item实时交换位置
- RecycleView实现拖拽交换item位置
- ListView 拖拽Item交换位置
- android 自定义ScrollView实现反弹效果(以及解决和ListView之间的冲突)