有时时候需要对ListView的Item进行手动拖拽排序,如安桌系统中的对通知栏的开关排序,因此需要自定义一个可拖拽的ListView,效果如下:
可见,该ListView只有已添加栏可以拖动,同时可以拖动到未添加栏中,且拖动到顶部或底部时,会自动滚动列表。实现的基本原理为:
1.当点击列表时,获取点击的itemView
int position = pointToPosition(x, y);
View itemView = getChildAt(position - getFirstVisiblePosition());
2.获取itemView的DrawingCache
itemView.setDrawingCacheEnabled(true); // 开启cache.
mBitmap = Bitmap.createBitmap(itemView.getDrawingCache()); // 根据cache创建一个新的bitmap对象.
itemView.setDrawingCacheEnabled(false);
3.根据拖拽的位置绘制DrawingCache
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
// 绘制拖拽的itemView,mLastY为触摸点的y坐标,mDragViewOffset为触摸点在itemView中的坐标y
if (mBitmap != null && !mBitmap.isRecycled()) {
canvas.drawBitmap(mBitmap, 0, mLastY - mDragViewOffset, null);
}
}
4.拖拽的过程中,判断是否交换位置
private void checkExchange(int y) {
if (mCurrentPosition != mLastPosition) { // // 数据交换
if (mDragItemListener != null) {
if (mDragItemListener.canExchange(mLastPosition, mCurrentPosition)) { // 进行数据交换,true则表示交换成功
View lastView = mItemView;
mItemView = getChildAt(mCurrentPosition - getFirstVisiblePosition());
// 通知交换数据成功,可在此时设置交换的动画效果
mDragItemListener.onExchange(mLastPosition, mCurrentPosition, lastView, mItemView);
mLastPosition = mCurrentPosition;
}
}
}
}
5.当移动到底部时,ListView向上滑动,当移动到顶部时,ListView要向下滑动
public void checkScroller(final int y) {
int offset = 0;
if (y < mAutoScrollUpY) { // 拖动到顶部,ListView需要下滑
if (y <= mDownY - mTouchSlop) {
offset = dp2px(getContext(), 6); // 滑动的距离
}
} else if (y > mAutoScrollDownY) { // 拖动到底部,ListView需要上滑
if (y >= mDownY + mTouchSlop) {
offset = -dp2px(getContext(), 6); // 滑动的距离
}
}
if (offset != 0) {
View view = getChildAt(mCurrentPosition - getFirstVisiblePosition());
if (view != null) {
// 滚动列表
setSelectionFromTop(mCurrentPosition, view.getTop() + offset);
if (!mScrolling) {
mScrolling = true;
long passed = System.currentTimeMillis() - mLastScrollTime;
postDelayed(mScrollRunnable, passed > 15 ? 15 : 15 - passed);
}
}
}
}
为了兼容不同的交互需求,定义了DragItemListener,把跟交互相关的代码交给外部。
public interface DragItemListener {
/**
* 是否进行数据交换
*
* @param srcPosition
* @param position 当前拖拽的view的索引
* @return 返回true,则确认数据交换;返回false则表示放弃
*/
boolean canExchange(int srcPosition, int position);
/**
* 当完成数据交换时回调
*
* @param srcPosition
* @param position 当前拖拽的view的索引
* @param srcItemView
* @param itemView 当前拖拽的view
*/
void onExchange(int srcPosition, int position, View srcItemView, View itemView);
/**
* 释放手指
*
* @param position
*/
void onRelease(int position, View itemView, int itemViewY, int releaseX, int releaseY);
/**
* 是否可以拖拽
*
* @param itemView
* @param x 当前触摸的坐标
* @param y
* @return
*/
boolean canDrag(View itemView, int x, int y);
/**
* 开始拖拽
*
* @param position
*/
void startDrag(int position, View itemView);
/**
* 在生成拖影(itemView.getDrawingCache())之前
*
* @param itemView
*/
void beforeDrawingCache(View itemView);
/**
* 在生成拖影(itemView.getDrawingCache())之后
*
* @param itemView
* @param bitmap 由itemView.getDrawingCache()生成
* @return 最终显示的拖影,如果返回为空则使用itemView.getDrawingCache()
*/
Bitmap afterDrawingCache(View itemView, Bitmap bitmap);
}
实践中会不断的改进的代码,请大家关注最新完整的代码:https://github.com/1993hzw/Androids