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

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/fly1183989782/article/details/43278393

前言

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

效果图


思路

  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 在滑动

@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:
            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 有动画效果
展开阅读全文

没有更多推荐了,返回首页