自己动手(一)──可拖动排序的 ListView(1)

来源:互联网 发布:网络喷子的心理分析 编辑:程序博客网 时间:2024/05/16 06:00

前言

相关的开源库有很多,也非常完善。然而,正因为非常完善,代码量很大。想要学习的时候,感觉无从下手,也意味着无法自己扩展。所以,我有个计划,把这些轮子自己再造一遍,明白其中的原理,在需要的时候,能够自己扩展。于此同时,如果你想要一个简单初级、容易理解的版本,也许这篇文章会有帮助。

参考

http://blog.csdn.net/jj120522/article/details/8240407 
https://github.com/bauerca/drag-sort-listview

效果图


思路

  1. 监听 TouchEvent
  2. 确定要拖动的 ItemView──DragItemView
  3. 生成DragItemView 的快照图像──DragItemViewBitmap
  4. 隐藏 DragItemView
  5. 在 onDraw 函数中绘制 DragItemViewBitmap
  6. 监听 onMove 事件,随时改变DragItemViewBitmap绘制位置
  7. 监听 onUp、onCancel 事件,通过 adapter 改变数据的次序,间接改变 ListView中 itemView 的顺序

关键源码

注:注释是用英文写的,没有语法可言,只为表达意思。 
另:在注释中引用自己的域或方法,可以用这种方式{@link #draggingItemViewBitmap}

public class DragSortListView extends ListView {    /**     * the mask image that moves as user finger moves     */    private Bitmap draggingItemViewBitmap;    /**     * the region that the {@link #draggingItemViewBitmap} will be drawn     */    private RectF draggingItemViewRect;    /**     * the paint used in drawing {@link #draggingItemViewBitmap}, new it in onDraw isn't recommended     */    private Paint draggingItemViewBitmapPaint;    /**     * the y coordinate of the beginning <code>ACTION_DOWN</code>, also the beginning of the whole gesture     */    private float downY;    /**     * the y coordinate of the last MotionEvent     */    private float lastY;    /**     * the original position of the draggingItemView     */    private int srcPosition;    /**     * the flag that indicate whether we are dragging some item view     */    private boolean dragging;    /**     * store it, so we can set it to be visible when dragging ends     */    private View draggingItemView;    @Override    public boolean onTouchEvent(MotionEvent ev) {        switch (ev.getAction()){            case MotionEvent.ACTION_DOWN:                if (handleDownEvent(ev)) {                    //return true means i am interested in this gesture                    return true;                }                break;            case MotionEvent.ACTION_MOVE:                if (dragging) {                    handleMoveEvent(ev);                    return true;                }                break;            case MotionEvent.ACTION_UP:            case MotionEvent.ACTION_CANCEL:                if (dragging) {                    handleUpEvent(ev);                    return true;                }                break;        }        return super.onTouchEvent(ev);    }    private boolean handleDownEvent(MotionEvent ev) {        float downX = ev.getX();        float downY = ev.getY();        int downPosition = pointToPosition(((int) downX), ((int) downY));        if (downPosition == AdapterView.INVALID_POSITION){            return false;        }        // get the item view under user's finger        View underFingerItemView = getChildAt(downPosition - getFirstVisiblePosition());        // check whether user's finger pressed on the drag handler        View dragHandler = underFingerItemView.findViewById(R.id.tv_drag_handler);        if (dragHandler == null) {            // if the under item view don't have a drag handler, don't start drag            return false;        }        Rect dragHandlerHitRect = new Rect();        dragHandler.getHitRect(dragHandlerHitRect);        //important! change the coordinate system from underFingerItemView to this listview.        dragHandlerHitRect.offset(((int) underFingerItemView.getLeft()), ((int) underFingerItemView.getTop()));        if (!dragHandlerHitRect.contains(((int) downX), ((int) downY))){            // if user didn't pressed on the drag handler, don't start drag            return false;        }        // now we can start drag        dragging = true;        draggingItemView = underFingerItemView;//        this.downY = downY;        lastY = downY;        draggingItemViewBitmap = getBitmapFromView(draggingItemView);//        underFingerItemView.getHitRect(draggingItemViewRect);//        underFingerItemView.getDrawingRect();//        Rect underFingerItemViewDrawingRect = new Rect();//        underFingerItemView.getDrawingRect(underFingerItemViewDrawingRect);//        underFingerItemView.getHitRect(underFingerItemViewDrawingRect); the same as up one, the same coordinate , the same size        draggingItemViewRect = new RectF();        draggingItemViewRect.set(draggingItemView.getLeft(), draggingItemView.getTop(), draggingItemView.getRight(), draggingItemView.getBottom());        draggingItemViewBitmapPaint = new Paint();        srcPosition = downPosition;        draggingItemView.setVisibility(INVISIBLE);        invalidate();        return true;    }    private void handleMoveEvent(MotionEvent ev) {        // update the position where the mask image will be drawn        float currY = ev.getY();        float dy = currY - lastY;        lastY = currY;        draggingItemViewRect.offset(0, dy);        // redraw        invalidate();    }    private void handleUpEvent(MotionEvent ev) {        // reset        dragging = false;        draggingItemViewBitmap = null;        draggingItemView.setVisibility(VISIBLE);        draggingItemView = null;        // reorder        int upPosition = pointToPosition(((int) ev.getX()), ((int) ev.getY()));        int dstPosition = upPosition;        if (upPosition == AdapterView.INVALID_POSITION){            dstPosition = srcPosition;        }        ((CommonDragSortAdapter) getAdapter()).moveItem(srcPosition, dstPosition);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        if (dragging) {            canvas.drawBitmap(draggingItemViewBitmap, draggingItemViewRect.left, draggingItemViewRect.top, draggingItemViewBitmapPaint);        }    }    public static Bitmap getBitmapFromView(View view){        view.setDrawingCacheEnabled(true);        // this is the important code :)        // Without it the view will have a dimension of 0,0 and the bitmap        // will be null//        view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),//                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));//        view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());//        view.buildDrawingCache(true);        Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());        // clear drawing cache//        view.setDrawingCacheEnabled(false);        return bitmap;    }}

实现过程中犯的错误

1.坐标参照系

Rect dragHandlerHitRect = new Rect();dragHandler.getHitRect(dragHandlerHitRect);if (!dragHandlerHitRect.contains(((int) downX), ((int) downY))){    // if user didn't pressed on the drag handler, don't start drag    return false;}

上面的代码不能得到预期结果。getHitRect得到的矩形是以其父视图为参考系的。所以,dragHandlerHitRect是以underFingerItemView为参考系的。然而,downXdownY是以整个 ListView 为参考系的。不在一个参考系的坐标的比较是没有意义的。解决办法:变换坐标参考系。dragHandlerHitRect.offset(((int) underFingerItemView.getLeft()), ((int) underFingerItemView.getTop())); 
2.mask image 没有随手指的移动而移动 
因为dy没有想清楚到底是用 currY-lastY,还是 currY-downY。 
3.item view getTop() 总是得到0

    public static Bitmap getBitmapFromView(View view){        view.setDrawingCacheEnabled(true);        // this is the important code :)        // Without it the view will have a dimension of 0,0 and the bitmap        // will be null        view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));        view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());        view.buildDrawingCache(true);        Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());        // clear drawing cache        view.setDrawingCacheEnabled(false);        return bitmap;    }

是受上面这个函数的影响了,让 item view重新布局了,所以才出现的问题。解决办法:删除重新布局代码。 
4.想要拖动 Item view 的时候,ListView 在滑动

@Overridepublic boolean onTouchEvent(MotionEvent ev) {    switch (ev.getAction()){        case MotionEvent.ACTION_DOWN:            if (handleDownEvent(ev)) {                //return true means i am interested in this gesture                return true;            }            break;        case MotionEvent.ACTION_MOVE:            handleMoveEvent(ev);            break;        case MotionEvent.ACTION_UP:        case MotionEvent.ACTION_CANCEL:            handleUpEvent(ev);            break;    }    return super.onTouchEvent(ev);}

让case MotionEvent.ACTION_MOVE、MotionEvent.ACTION_UP、MotionEvent.ACTION_CANCEL在 dragging 状态下也返回 true 即可 
5.mask image 和 dragging item view 不相符 
不用 viewholder 模式就没事。

    public View getView(int position, View convertView, ViewGroup parent) {        CommonViewHolder<T> holder = null;//        if (convertView == null) {            convertView = LayoutInflater.from(context).inflate(layout, parent, false);            try {                holder = viewHolderClazz.getDeclaredConstructor(View.class).newInstance(convertView);            } catch (NoSuchMethodException e){                e.printStackTrace();                Log.e(TAG, e.toString());            } catch (SecurityException e){                e.printStackTrace();                Log.e(TAG, e.toString());            } catch (Exception e) {                e.printStackTrace();                Log.e(TAG, e.toString());            }            convertView.setTag(holder);//        } else {//            holder = (CommonViewHolder<T>) convertView.getTag();//        }        holder.setItem(datas.get(position));        return convertView;    }

TODO

  1. 兼容 viewholder 模式
  2. 在 dragging 的过程中,如果手指移动到ListView上边缘(下边缘),让ListView自动向下滑动(向上滑动)
  3. 实时的 reorder Item view
  4. 让 dragging item view有一定的透明度
  5. 使reorder item view 有动画效果

完整工程

https://github.com/myronlee/DragSortListView

0 0
原创粉丝点击