ListView拖拽交换 item 的实现(QQ 分组管理功能)
2015-03-18 23:17
721 查看
在写这篇文章前,碰巧看到有个哥们也做了这个功能,【Android】可拖拽排序的ListView。而且就在几个小时前发表的,本来想还是算了,我就不写这个功能,不过我大致浏览了他的实现原理跟我的实现原理还是有很大差别,所以还是决定写这样一篇文章,因为我相信大家看文章,更多是想了解其中的原理,而非单纯的为了实现某个功能。只有了解了原理,才能扩展,实现更多的功能。
好了,回到正题,先来看效果图
2、 在 ListView 中根据手势滑动不断的绘制 Drawable;
3、Drawable每到达另一个 Item的位置,就替换这个 item,并设置动画效果;
4、当 Drawable 滑到屏幕的最下方或者最上方,且此 ListView 可以滚动,这 listView 执行滚动。
开篇就讲了,此功能的实现方式不止一种,所以有兴趣了解其他实现原理可以看 [【Android】可拖拽排序的ListView]这篇文章。
通过 View 获取 bitmapDrawable
通过 position 获取 View
因为我们一般在 ListView 的 adapter 中都会复用 View,所以这里需要做一个 position 的转化
通过 View,获取 Drawable
这里获取的是一个 BitmapDrawable,其实也就是一个 Drawable,BitmapDrawable继承之Drawable,所以很多方法可以共用;并且设置了它的位置,跟所选中的 item 的位置一样,
bitmapDrawable.setBounds(mCellCurrentBounds);
接着是把 Drawable 绘制在 ListView 上面
调用invalidate()重绘方法,这时的效果图已经可以看到了
监听手势滑动
手势滑动的时候不断改变 drawable 的 bounds,并invalidate(),这时我们所获取的 BitmapDrawable 就会不断的改变位置,具体怎么改变呢,看代码
在 Drawable 改变位置的时候,我们是要不断的检测,是否达到交换 item 的条件,如果达到了,则执行交换,并设置相应的交换动画效果。
检测和执行 item 交换
在 Drawable 改变位置的时候,还有一件事我们不能忘了,就是要判断 Drawable 所在的 item 是否已经到了 ListView 不可见的位置,比如我们这里的屏幕顶部或者底部,如果到了,则触发 ListView 滚动
判断和执行 ListView 滚动
其实上面判断条件如果严谨一点还需要判断当前 ListVeiw 是否到了最顶端,或者最低端,也就是不能滑动的时候,如果到了,则不让滑动,当然,没有判断也没有关系,因为如果到了滑不动的时候,系统默认不能滑动。
在 ListView 滚动的时候还需要检测和交换 item
最后就是放开手指的时候的操作了
ListView拖拽交换 item 的核心实现就是以上内容了,其他只要稍微注意一下;
1、通过 View 创建 Drawable,这里主要用到 Bitmap、Canvas两个类,首先创建一个空的 Bitmap,其次创建一个 Canvas并把 Bitmap 传入,这个时候,我的理解是,用 Bitmap 来作为画布,再把 View 通过 onDraw(Canvas c)绘制 View 的内容到画布上。
2、手势监听,这个不多解释了!
3、属性动画的运用。这里主要是对 ObjectAnimator做了最基本的使用,也非常简单。
4、ListView 通过 position 来获取 item 的 View,不能直接通过 getChildAt(int i),因为 item 一般都是复用的。
点击下载源码
好了,回到正题,先来看效果图
分析原理
1、长按获取所按 Item 的 View,并通过View 生成一个 Drawable;2、 在 ListView 中根据手势滑动不断的绘制 Drawable;
3、Drawable每到达另一个 Item的位置,就替换这个 item,并设置动画效果;
4、当 Drawable 滑到屏幕的最下方或者最上方,且此 ListView 可以滚动,这 listView 执行滚动。
开篇就讲了,此功能的实现方式不止一种,所以有兴趣了解其他实现原理可以看 [【Android】可拖拽排序的ListView]这篇文章。
分步实现
这里就按照原理来一步一步实现此功能,先来看如何获取 Drawable通过 View 获取 bitmapDrawable
通过 position 获取 View
/*获取Item的View*/ View mobileView = getViewForPosition(position);
因为我们一般在 ListView 的 adapter 中都会复用 View,所以这里需要做一个 position 的转化
/** * 通过位置获取View * * @param position * @return */ public View getViewForPosition(int position) { int itemPosition = position - getFirstVisiblePosition(); View view = getChildAt(itemPosition); return view; }
通过 View,获取 Drawable
/** * 创建一个 * BitmapDrawable * @param view * @return */ private BitmapDrawable createDrawable(View view) { BitmapDrawable bitmapDrawable = null; /*获取位置变量*/ int left = view.getLeft(); int top = view.getTop(); int right = left + view.getMeasuredWidth(); int bottom = top + view.getMeasuredHeight(); /*首先创建一个Bitmap,这个位图为空位图,里面啥也没有*/ Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); /*创建一个画布,把空位图放入画布中*/ Canvas canvas = new Canvas(bitmap); /*把所选的item的View 绘制到画布上,其实也就是会知道了Bitmap上*/ view.draw(canvas); /*绘制边矩形*/ Rect rect = new Rect(0,0,bitmap.getWidth(),bitmap.getHeight()) ; Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG) ; paint.setStrokeWidth(12); paint.setStyle(Paint.Style.STROKE); paint.setColor(0xff1f8c03); canvas.drawRect(rect, paint); /*把bitmap转化成BitmapDrawable*/ bitmapDrawable = new BitmapDrawable(getResources(), bitmap); /*设置drawable的原始位置*/ mCellOriginBounds = new Rect(left, top, right, bottom); /*初始化drawable的当前位置*/ mCellCurrentBounds = new Rect(mCellOriginBounds); bitmapDrawable.setBounds(mCellCurrentBounds); /*设置drawable的透明度*/ bitmapDrawable.setAlpha(120); return bitmapDrawable; }
这里获取的是一个 BitmapDrawable,其实也就是一个 Drawable,BitmapDrawable继承之Drawable,所以很多方法可以共用;并且设置了它的位置,跟所选中的 item 的位置一样,
bitmapDrawable.setBounds(mCellCurrentBounds);
接着是把 Drawable 绘制在 ListView 上面
/*绘制Drawable*/ @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (mMobileView != null) { mMobileView.draw(canvas); } }
调用invalidate()重绘方法,这时的效果图已经可以看到了
监听手势滑动
手势滑动的时候不断改变 drawable 的 bounds,并invalidate(),这时我们所获取的 BitmapDrawable 就会不断的改变位置,具体怎么改变呢,看代码
@Override public boolean onTouchEvent(MotionEvent ev) { final int action = ev.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: /*这里是获取按下的时候点的坐标位置*/ int pointIndex = MotionEventCompat.getActionIndex(ev); mActivityPointId = MotionEventCompat.getPointerId(ev, pointIndex); if (mActivityPointId == INVALID) { break; } mDownMotionY = (int) MotionEventCompat.getY(ev, pointIndex); break; case MotionEvent.ACTION_MOVE: if (isMove) { int pointMoveIndex = MotionEventCompat.getActionIndex(ev); mActivityPointId = MotionEventCompat.getPointerId(ev, pointMoveIndex); if (mActivityPointId == INVALID) { break; } mCurrentMotionY = (int) MotionEventCompat.getY(ev, mActivityPointId); int delay = mCurrentMotionY - mDownMotionY; /*偏移Drawable的位置*/ mCellCurrentBounds.offsetTo(mCellCurrentBounds.left, mCellOriginBounds.top + delay + mScrollOffset); mMobileView.setBounds(mCellCurrentBounds); /*去判断是否需要交换数据*/ checkExchangeItem(); isScroll = false; /*判断是否需要滚动ListView*/ isScroll = checkScroll(); /*然后重绘*/ invalidate(); return false; } break; case MotionEvent.ACTION_UP: /*松手执行结束拖拽操作*/ touchEventEnd(); break; case MotionEvent.ACTION_POINTER_UP: int pointUpIndex = MotionEventCompat.getActionIndex(ev); int pointId = MotionEventCompat.getPointerId(ev, pointUpIndex); if (pointId == mActivityPointId) { /*松手执行结束拖拽操作*/ touchEventEnd(); } break; case MotionEvent.ACTION_CANCEL: /*这里直接复原*/ touchEventCancel(); break; } return super.onTouchEvent(ev); }
在 Drawable 改变位置的时候,我们是要不断的检测,是否达到交换 item 的条件,如果达到了,则执行交换,并设置相应的交换动画效果。
检测和执行 item 交换
/** * 检测是否需要交换数据,如果需要则交换 */ private void checkExchangeItem() { /*这个如果是初始化的时候调用,则直接返回*/ if (mCellOriginBounds == null) { return; } /*获取滑动两点之间的差值*/ final int deltaY = mCurrentMotionY - mDownMotionY; /*获取滑动的总值*/ int deltaYTotal = mCellOriginBounds.top + mScrollOffset + deltaY; /*获取相邻两个View 和自己的View*/ View belowView = getViewForID(mBelowItemId); View mobileView = getViewForID(mMobileItemId); View aboveView = getViewForID(mAboveItemId); /*判断是向下还是向上滑动*/ boolean isBelow = (belowView != null) && (deltaYTotal > belowView.getTop()); boolean isAbove = (aboveView != null) && (deltaYTotal < aboveView.getTop()); if (isAbove || isBelow) { final long switchItemID = isBelow ? mBelowItemId : mAboveItemId; /*获取下一个 item 的View*/ View switchView = isBelow ? belowView : aboveView; /*获取原始 item 的 position,用于数据交换*/ final int originalItem = getPositionForView(mobileView); /*真正交换两个数据*/ SwapItem(originalItem, getPositionForView(switchView)); /*设置显示和隐藏View*/ switchView.setVisibility(INVISIBLE); mobileView.setVisibility(VISIBLE); /*更新Adapter 的数据*/ ((BaseAdapter) getAdapter()).notifyDataSetChanged(); /*重新赋值*/ mDownMotionY = mCurrentMotionY; /*更新相邻两个 item 的 id*/ updateNeighborViewsForID(mMobileItemId); /*设置偏移量,这个的作用是用于ListView 的滚动*/ mScrollOffset = mScrollOffset + deltaY; /*----------以下部分为动画效果部分---------------*/ View swappedView = getViewForID(switchItemID) ; int switchViewNewTop = swappedView.getTop(); int switchTop = switchView.getTop() ; int delta = switchTop - switchViewNewTop ; swappedView.setTranslationY(delta); ObjectAnimator animator = ObjectAnimator.ofFloat(swappedView, "translationY",0); animator.setDuration(MOVE_DURATION); animator.start(); } } /** * 根据位置 交换两个数据, * * @param indexOne * @param indexTwo */ private void SwapItem(int indexOne, int indexTwo) { Object temp = mList.get(indexOne); mList.set(indexOne, mList.get(indexTwo)); mList.set(indexTwo, temp); }
在 Drawable 改变位置的时候,还有一件事我们不能忘了,就是要判断 Drawable 所在的 item 是否已经到了 ListView 不可见的位置,比如我们这里的屏幕顶部或者底部,如果到了,则触发 ListView 滚动
判断和执行 ListView 滚动
/** * 检测是否可以滚懂 * @return */ private boolean checkScroll() { int height = getHeight(); /*如果当前 item 的顶部小于0,即到了 listView 的顶部不可见的位置。*/ if (mCellCurrentBounds.top < 0) { smoothScrollBy(-4, 0); return true; } /*当当前 item 的底部大于ListView 的高度,即到了 listView 的低部不可见的位置*/ if (mCellCurrentBounds.top + mCellCurrentBounds.height() > height ) { smoothScrollBy(4, 0); return true; } return false; }
其实上面判断条件如果严谨一点还需要判断当前 ListVeiw 是否到了最顶端,或者最低端,也就是不能滑动的时候,如果到了,则不让滑动,当然,没有判断也没有关系,因为如果到了滑不动的时候,系统默认不能滑动。
在 ListView 滚动的时候还需要检测和交换 item
private class OnScrollerImpl implements OnScrollListener { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { /*停止滚动,继续判断是否符合滚动的条件*/ if (isMove && isScroll) { /**/ isScroll = checkScroll(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (isMove) { /*检测和交换 item*/ checkExchangeItem(); /*更新相邻的两个 itemId*/ updateNeighborViewsForID(mMobileItemId); } } }
最后就是放开手指的时候的操作了
/** * */ private void touchEventEnd() { final View mobileView = getViewForID(mMobileItemId); if (isMove){ isMove = false; isScroll = false; mActivityPointId = INVALID; /*------------放开手指时的结束动画-------------*/ mCellCurrentBounds.offsetTo(mCellOriginBounds.left, mobileView.getTop()); ObjectAnimator hoverViewAnimator = ObjectAnimator.ofObject(mMobileView, "bounds", sBoundEvaluator, mCellCurrentBounds); hoverViewAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { invalidate(); } }); hoverViewAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { setEnabled(false); } @Override public void onAnimationEnd(Animator animation) { mAboveItemId = INVALID; mMobileItemId = INVALID; mBelowItemId = INVALID; mobileView.setVisibility(VISIBLE); mMobileView = null; setEnabled(true); invalidate(); } }); hoverViewAnimator.start(); }else { touchEventCancel(); } }
ListView拖拽交换 item 的核心实现就是以上内容了,其他只要稍微注意一下;
总结:
发现很多篇文章没写总结了,以后的文章最好还是写一下,算是对整篇文章知识的一个回顾;1、通过 View 创建 Drawable,这里主要用到 Bitmap、Canvas两个类,首先创建一个空的 Bitmap,其次创建一个 Canvas并把 Bitmap 传入,这个时候,我的理解是,用 Bitmap 来作为画布,再把 View 通过 onDraw(Canvas c)绘制 View 的内容到画布上。
2、手势监听,这个不多解释了!
3、属性动画的运用。这里主要是对 ObjectAnimator做了最基本的使用,也非常简单。
4、ListView 通过 position 来获取 item 的 View,不能直接通过 getChildAt(int i),因为 item 一般都是复用的。
点击下载源码
相关文章推荐
- ListView的Item中的图片拖拽功能的实现
- ExpandableListView实例(三)_实现QQ中"未分组"效果和"未分组"不可编辑删除功能
- ListView的Item中的图片拖拽功能的实现
- Android自定义ListView实现仿QQ可拖拽列表功能
- Android使用Item Swipemenulistview实现仿QQ侧滑删除功能
- ListView 实现IM 类似QQ 微信 item置顶最简单的方法
- Android 可拖拽的GridView效果实现, 长按可拖拽和item实时交换
- Android 可拖拽的GridView效果实现, 长按可拖拽和item实时交换
- PHP实现Google plus的好友拖拽分组功能(1)
- Android 可拖拽的GridView效果实现, 长按可拖拽和item实时交换
- Android 可拖拽的GridView效果实现, 长按可拖拽和item实时交换
- Android 可拖拽的GridView效果实现, 长按可拖拽和item实时交换
- Android 可拖拽的GridView效果实现, 长按可拖拽和item实时交换
- VS2008中,MFC对话框类实现类似QQ拖拽上传文件的功能,CWnd::OnDropFiles
- Android 可拖拽的GridView效果实现, 长按可拖拽和item实时交换
- Android 可拖拽的GridView效果实现, 长按可拖拽和item实时交换
- 实现可以直接粘QQ贴截图的bug管理功能
- 关于如何实现美团网的导航功能并且能重用ListView的Item(下载免费了)
- listview 实现微信删除功能向左移动item出现隐藏的删除按钮功能终于实现了,分享总结一下。(跟微信删除一样额)
- C# listview如何显示网格线以及如何实现item的选中功能