`

Android实现可拖拽的ListView

 
阅读更多

通过继承 ListView 实现可拖拽的 ListView ,先说说实现拖拽的原理吧,实现拖拽需要考虑三个问题:第一怎么确定你在拖拽 listview 里面的 item 的时候就是你手指当前选中的 item ;第二实现拖拽的效果,就是有一个浮动的层跟随你的手指在移动;第三你放开手指时怎么把你拖拽的这个 item 放到当前 listView 的位置(也就是说改变 item 的位置)。 明白了这三个问题就比较好实现了。

里面会涉及到一些比较重要的方法调用,首先是 pointToPosition(int x, int y) 这方方法 Android 官方的解释是 ” Maps a point to a position in the list” 我把它理解为通过 x y 的位置来确定这个 listView 里面这个 item 的位置。 有了这个方法就解决了第一和第三个问题了。接下来我们可以通过 WindowManager 来解决第二个问题,然后通过 pointToPosition 方法就可以获取你手指按下时的 item 这个 item 其实就是你 listview 里面的 item ,这样的就可以把这个 item 设置为是 WindowManager view ,这样的话拖动的层的效果就模拟出来了,接下来是怎么让这个 WindowManager 跟随你的手指在移动。这个时候会涉及到 WindowManager 里面的 updateViewLayout(view, layoutparams) 来刷新 WindowManager 的位置,这样就实现了 WindowManager 会跟随你的手指在移动。最后就剩下你放下手指的时候怎么让你拖拽的 item 插入到 listview 里面,这个插入的动作其实包含了移除和插 入这两个动作。这个时候你可能会问在某个位置插入这个 item 需要 ”position” ”item” 两个参数, position 我们可以通过 pointToPosition 方法来获取,然后要插入的“ item ”其实是你 adapter 里面数据。因为我们上面的一系列动作都是在 listview 里面完成的,但是在我们重写 listview 的时候是还没有给 listview 设置 adapter 是吧,这个问题的我们通过在重写 listview 的类中自定义一个接口,然后你在 activity 里面初始化 listview 数据的时候实现这个接口。接口里面只有一个方法,方法里面的两个参数一个是你开始拖拽的的 item 的位置,另一个是你拖拽移动之后之后的 item 的位置。下面我们看看效果吧:

 

 

        private final float mAlpha = 0.9f;
	//拖动的view
	private ImageView mDragView;
	private Context mContext;
	private WindowManager mWindowManager;
	private WindowManager.LayoutParams mLayoutParams;
	//开始拖动时的位置
	private int mDragStartPosition;
	//当前的位置
	private int mDragCurrentPostion;
	//在滑动的时候,手的移动要大于这个返回的距离值才开始移动控件
	private int mScaledTouchSlop;
	//当前位置距离边界的位置
	private int mDragOffsetX;
	private int mDragOffSetY;
	//移动的位置
	private int mDragPointX;
	private int mDragPointY;
	//边界
	private int mUpperBound;
        private int mLowerBound;
	private DropViewListener mDropViewListener;
	
	public DragListView(Context context) {
		super(context);
		mContext = context;
		mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		mScaledTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
	}

	public DragListView(Context context, AttributeSet attr) {
		super(context, attr);
		mContext = context;
		mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		mScaledTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
	}
 

 我们在onInterceptTouchEvent方法里面作初始化动作:

 

 

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //ev.getX()相对于控件本身左上角,ev.getRawX()相对于容器圆点位置
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                final int x = (int) ev.getX();//相对于空间本身
                final int y = (int) ev.getY();
                final int itemNum = pointToPosition(x, y);
                if(itemNum == AdapterView.INVALID_POSITION){
                    break;
                }
                final ViewGroup item = (ViewGroup) getChildAt(itemNum - getFirstVisiblePosition());
                mDragPointX = x - item.getLeft();
                mDragPointY = y - item.getTop();
                mDragOffsetX = ((int) ev.getRawX()) - x;
                mDragOffSetY = ((int) ev.getRawY()) - y;
               
                //长按
                item.setOnLongClickListener(new OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View v) {
                        //计算边界
                        final int height = getHeight();
                        mUpperBound = Math.min(y - mScaledTouchSlop, height / 3);
                        mLowerBound = Math.max(y + mScaledTouchSlop, height * 2 / 3);
                        mDragCurrentPostion = mDragStartPosition = itemNum;
                       
                        item.setDrawingCacheEnabled(true);
                        Bitmap bitmap = Bitmap.createBitmap(item.getDrawingCache());
                        startDragging(bitmap, x, y);
                        return true;
                    }
                });
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

这里面我们就记录了拖拽的当前和开始时位置mDragCurrentPostion和 mDragStartPosition,并通过startDragging来初始化mWindowManager,在startDragging方法里面调用的stopDragging方法其实是一个内存释放的过程,这个方法做的事情就是释放内存空间。

 

private void startDragging(Bitmap bitm, int x, int y){
        stopDragging();
       
        mLayoutParams = new WindowManager.LayoutParams();
        mLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
        mLayoutParams.x = x - mDragPointX + mDragOffsetX;
        mLayoutParams.y = y - mDragPointY + mDragOffSetY;
        mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
        mLayoutParams.format = PixelFormat.TRANSLUCENT;
        mLayoutParams.windowAnimations = 0;
       
        ImageView imageView = new ImageView(mContext);
        imageView.setImageBitmap(bitm);
        imageView.setBackgroundResource(R.drawable.tab_item_bg);
        imageView.setPadding(0, 0, 0, 0);
        mWindowManager.addView(imageView, mLayoutParams);
        mDragView = imageView;
    }

private void stopDragging(){
        if(mDragView != null){
            mWindowManager.removeView(mDragView);
            mDragView.setImageDrawable(null);
            mDragView = null;
        }
    }

我们在onInterceptTouchEvent方法记录了拖拽的开始位置和当前位置,并且初始化了windowmanager,接下来我们通过onTouchEvent方法来实现让windowmanager跟随你的手指移动

@Override
    public boolean onTouchEvent(MotionEvent ev) {
        if(mDragView != null && mDragCurrentPostion != INVALID_POSITION && mDropViewListener != null){
            switch (ev.getAction()) {
                case MotionEvent.ACTION_UP:
                    //int y = (int) ev.getY();
                    stopDragging();
                    //数据交换
                    if(mDragCurrentPostion >= 0 && mDragCurrentPostion < getCount()){
                        mDropViewListener.drop(mDragStartPosition, mDragCurrentPostion);
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    int x = (int) ev.getX();
                    int y = (int) ev.getY();
                    dragView(x, y);
                    if (y >= getHeight() / 3) {
                        mUpperBound = getHeight() / 3;
                    }
                    if (y <= getHeight() * 2 / 3) {
                        mLowerBound = getHeight() * 2 / 3;
                    }
                     int speed = 0;
                    if (y > mLowerBound) {
                        if (getLastVisiblePosition() < getCount() - 1) {
                            speed = y > (getHeight() + mLowerBound) / 2 ? 16 : 4;
                        } else {
                            speed = 1;
                        }
                    } else if (y < mUpperBound) {
                        speed = y < mUpperBound / 2 ? -16 : -4;
                        if (getFirstVisiblePosition() == 0
                                && getChildAt(0).getTop() >= getPaddingTop()) {
                            speed = 0;
                        }
                    }
                    if (speed != 0) {
                        smoothScrollBy(speed, 30);
                    }
                    break;
            }
            return true;
        }
        return super.onTouchEvent(ev);
    }

其中dragView方法就是根据你手指移动来改变windowmanager的位置,在移动的时候我们需要考虑的另外一个问题是你的listview滚动条的时候,这个时候我们需要考虑边界,不然会抛出空指针异常。

private void dragView(int x, int y){
        if(mDragView != null){
            mLayoutParams.alpha = mAlpha;
            mLayoutParams.y = y - mDragPointY + mDragOffSetY;
            mLayoutParams.x = x - mDragPointX + mDragOffsetX;
            mWindowManager.updateViewLayout(mDragView, mLayoutParams);
        }
        int tempPosition = pointToPosition(0, y);
        if(tempPosition != INVALID_POSITION){
            mDragCurrentPostion = tempPosition;
        }
       
        //滚动
        int scrollY = 0;
        if(y < mUpperBound){
            scrollY = 8;
        }else if(y > mLowerBound){
            scrollY = -8;
        }
       
        if(scrollY != 0){
            int top = getChildAt(mDragCurrentPostion - getFirstVisiblePosition()).getTop();
            setSelectionFromTop(mDragCurrentPostion, top + scrollY);
        }
    }

 这样的话就完成了拖拽的效果,剩下的是你放开手指,然后把你拖拽的item插入到listview的列表里面,我们定义了一个接口

1 public void setDropViewListener(DropViewListener listener){
2          this .mDropViewListener = listener;
3      }
4       
5      public interface DropViewListener {
6          void drop( int from, int to);
7      }

这样的话,你在初始化listview给listView设置adapter的时候需要实现DropViewListener接口,from和to两个参数分别是你拖拽时的最初位置和你移动后的位置,这样的话你在activity里面就可以实现数据插入了。 注意:
但是如果你的这listview的item包含了复选框的话,这个时候 listview的onitemclicklistener事件就会失效,也就是说你不能通过你平时处理listview的 onitemclicklistener的方法一样处理,你也可以通过自定义接口来模拟onitemclicklistener事件

 

 

   return true;http://blog.csdn.net/chenjie19891104/article/details/7031713

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics