Android實現可拖拽的ListView

openkk 12年前發布 | 80K 次閱讀 Android Android開發 移動開發

通過繼承ListView實現可拖拽的ListView,先說說實現拖拽的原理吧,實現拖拽需要考慮三個問題:第一怎么確定你在拖拽listview里面的item的時候就是你手指當前選中的item;第二實現拖拽的效果,就是有一個浮動的層跟隨你的手指在移動;第三你放開手指時怎么把你拖拽的這個item放到當前listView的位置(也就是說改變item的位置)。明白了這三個問題就比較好實現了。

里面會涉及到一些比較重要的方法調用,首先是pointToPosition(int x, int y)這方方法Android 官方的解釋是” Maps a point to a position in the list”我把它理解為通過xy的位置來確定這個listView里面這個item的位置。有了這個方法就解決了第一和第三個問題了。接下來我們可以通過WindowManager來解決第二個問題,然后通過pointToPosition方法就可以獲取你手指按下時的item這個item其實就是你listview里面的item,這樣的就可以把這個item設置為是WindowManagerview,這樣的話拖動的層的效果就模擬出來了,接下來是怎么讓這個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的位置。下面我們看看效果吧:

Android實現可拖拽的ListViewAndroid實現可拖拽的ListViewAndroid實現可拖拽的ListView下面我們看看代碼:

        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的列表里面,我們定義了一個接口

public void setDropViewListener(DropViewListener listener){
        this.mDropViewListener = listener;
    }

    public interface DropViewListener {
        void drop(int from, int to);
    }

這樣的話,你在初始化listview給listView設置adapter的時候需要實現DropViewListener接口,from和to兩個參數分別是你拖拽時的最初位置和你移動后的位置,這樣的話你在activity里面就可以實現數據插入了。

注意:
但是如果你的這listview的item包含了復選框的話,這個時候listview的onitemclicklistener事件就會失效,也就是說你不能通過你平時處理listview的onitemclicklistener的方法一樣處理,你也可以通過自定義接口來模擬onitemclicklistener事件。

 

 本文由用戶 openkk 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!