Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android自定義View——可拖拽的ListView

Android自定義View——可拖拽的ListView

編輯:關於Android編程

有時時候需要對ListView的Item進行手動拖拽排序,如安桌系統中的對通知欄的開關排序,因此需要自定義一個可拖拽的ListView,效果如下:

\

可見,該ListView只有已添加欄可以拖動,且拖動到頂部或底部時,會自動滾動列表。實現的原理為:

1.當點擊列表時,獲取點擊的itemView:

int position = pointToPosition(x, y);
View itemView = getChildAt(position - getFirstVisiblePosition());

2.獲取itemView的cache:
itemView.setDrawingCacheEnabled(true); // 開啟cache.
mBitmap = Bitmap.createBitmap(itemView.getDrawingCache()); // 根據cache創建一個新的bitmap對象.
itemView.setDrawingCacheEnabled(false);

 

3.根據拖拽的位置繪制cache:

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.拖拽的過程中,判斷是否交換位置:
int position = pointToPosition(0, y); // 當前拖拽到的位置
if (position != mLastPosition) { // 位置發生變化
    // onExchagne(mLastPosition, position), 交換adapter中的數據集
    mLastPosition = position;
}

DragListView的代碼如下:
public class DragListView extends ListView {

    private int mLastPosition; // 上次的位置,用於判斷是否跟當前交換位置
    private int mCurrentPosition; // 手指點擊准備拖動的時候,當前拖動項在列表中的位置.

    private int mAutoScrollUpY; // 拖動的時候,開始向上滾動的邊界
    private int mAutoScrollDownY; // 拖動的時候,開始向下滾動的邊界

    private int mLastY;

    private int mDragViewOffset; // 觸摸點在itemView中的高度

    private DragItemListener mDragItemListener;

    private boolean mHasStart = false;

    private Bitmap mBitmap; // 拖拽的itemView圖像

    private View mItemView;

    private boolean mIsHideItemView = true; // 拖拽時是否隱藏itemView

    public DragListView(Context context) {
        this(context, null);
    }

    public DragListView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DragListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * 觸摸事件處理
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_OUTSIDE:
            case MotionEvent.ACTION_CANCEL:
                if (mBitmap != null) {
                    stopDrag();
                    invalidate();
                    return true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (mBitmap != null) {
                    if (!mHasStart) {
                        mDragItemListener.startDrag(mCurrentPosition);
                        mHasStart = true;
                    }
                    int moveY = (int) ev.getY();
                    if (moveY < 0) { // 限制觸摸范圍在ListView中
                        moveY = 0;
                    } else if (moveY > getHeight()) {
                        moveY = getHeight();
                    }
                    onMove(moveY);
                    mLastY = moveY;
                    invalidate();
                    return true;
                }
                break;
            case MotionEvent.ACTION_DOWN: // 判斷是否進拖拽
                stopDrag();
                int x = (int) ev.getX(); // 獲取相對與ListView的x坐標
                int y = (int) ev.getY(); // 獲取相應與ListView的y坐標
                int temp = pointToPosition(x, y);
                if (temp == AdapterView.INVALID_POSITION) { // 無效不進行處理
                    return super.dispatchTouchEvent(ev);
                }
                mLastPosition = mCurrentPosition = temp;

                // 獲取當前位置的視圖(可見狀態)
                ViewGroup itemView = (ViewGroup) getChildAt(mCurrentPosition - getFirstVisiblePosition());

                if (itemView != null && mDragItemListener != null && mDragItemListener.canDrag(itemView, x, y)) {

                    // 觸摸點在item項中的高度
                    mDragViewOffset = y - itemView.getTop();
                    setLayerType(View.LAYER_TYPE_SOFTWARE, null); // 關閉硬件加速
                    mDragItemListener.beforeDrawingCache(itemView);
                    itemView.setDrawingCacheEnabled(true); // 開啟cache.
                    mBitmap = Bitmap.createBitmap(itemView.getDrawingCache()); // 根據cache創建一個新的bitmap對象.
                    itemView.setDrawingCacheEnabled(false);
                    mDragItemListener.afterDrawingCache(itemView);
                    mHasStart = false;
                    mLastY = y;

                    if (mIsHideItemView) { // 隱藏itemView
                        hideItemView();
                    }
                    invalidate();
                    return true;
                }
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        // 繪制拖拽的itemView
        if (mBitmap != null && !mBitmap.isRecycled()) {
            canvas.drawBitmap(mBitmap, 0, mLastY - mDragViewOffset, null);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        mAutoScrollUpY = w / 4; // 取得向上滾動的邊際,大概為該控件的1/3
        mAutoScrollDownY = h * 3 / 4; // 取得向下滾動的邊際,大概為該控件的2/3
    }


    /**
     * 拖動執行,在Move方法中執行
     *
     * @param y
     */
    public void onMove(int y) {
        // 為了避免滑動到分割線的時候,返回-1的問題
        int tempPosition = pointToPosition(0, y);
        if (tempPosition != INVALID_POSITION) {
            mCurrentPosition = tempPosition;
        }
        if (y < getChildAt(0).getTop()) { // 超出邊界處理(如果向上超過第二項Top的話,那麼就放置在第一個位置)
            mCurrentPosition = 0;
        } else if (y > getChildAt(getChildCount() - 1).getBottom()) { // // 如果拖動超過最後一項的最下邊那麼就防止在最下邊
            mCurrentPosition = getAdapter().getCount() - 1;
        }
        checkExchange(y); // 時時交換
        checkScroller(y); // listview移動.
    }

    /***
     * ListView的移動.
     * 要明白移動原理:當我移動到下端的時候,ListView向上滑動,當我移動到上端的時候,ListView要向下滑動。正好和實際的相反.
     */

    public void checkScroller(int y) {
        int offset = 0;
        if (y < mAutoScrollUpY) { // ListView需要下滑
            if (y < mLastY) {
                offset = (mAutoScrollUpY - y) / 5; // 時時步伐
            }
        } else if (y > mAutoScrollDownY) { // ListView需要上滑
            if (y > mLastY) {
                offset = (mAutoScrollDownY - y) / 5; // 時時步伐
            }
        }

        if (offset != 0) {
            View view = getChildAt(mCurrentPosition - getFirstVisiblePosition());
            if (view != null) {
                setSelectionFromTop(mCurrentPosition, view.getTop() + offset);
            }
        }

    }

    /**
     * 停止拖動,刪除影像
     */
    public void stopDrag() {
        if (mBitmap != null) {
            mBitmap.recycle();
            mBitmap = null;
            if (mDragItemListener != null) {
                mDragItemListener.onRelease(mCurrentPosition);
            }
        }
        if (mItemView != null) {
            mItemView.setVisibility(View.VISIBLE);
            mItemView = null;
        }
    }

    /***
     * 拖動時時change
     */
    private void checkExchange(int y) {
// 數據交換
        if (mCurrentPosition != mLastPosition) {
            if (mDragItemListener != null) {
                if (mDragItemListener.onExchange(mLastPosition, mCurrentPosition)) { // 進行數據交換,true則表示交換成功
                    mLastPosition = mCurrentPosition;

                    if (mIsHideItemView) { // 隱藏實際的itemView
                        hideItemView();
                    }
                }
            }
        }
    }

    // 隱藏實際的itemView
    private void hideItemView() {
        if (mItemView != null) {
            mItemView.setVisibility(View.VISIBLE);
        }
        mItemView = getChildAt(mCurrentPosition - getFirstVisiblePosition()); // 隱藏實際的itemView
        if (mItemView != null) {
            mItemView.setVisibility(View.INVISIBLE);
        }
    }

    public void setDragItemListener(DragItemListener listener) {
        mDragItemListener = listener;
    }

    public DragItemListener getDragListener() {
        return mDragItemListener;
    }

    /**
     *  拖拽監聽器
     */
    public interface DragItemListener {

        /**
         * 數據交換
         *
         * @param srcPosition
         * @param position
         * @return 返回true,則確認數據交換;返回false則表示放棄
         */
        boolean onExchange(int srcPosition, int position);

        /**
         * 釋放手指
         *
         * @param position
         */
        void onRelease(int position);

        /**
         * 是否可以拖拽
         *
         * @param itemView
         * @param x        當前觸摸的坐標
         * @param y
         * @return
         */
        boolean canDrag(View itemView, int x, int y);

        /**
         * 開始拖拽
         *
         * @param position
         */
        void startDrag(int position);

        /**
         * 在生成拖影(itemView.getDrawingCache())之前
         *
         * @param itemView
         */
        void beforeDrawingCache(View itemView);

        /**
         * 在生成拖影(itemView.getDrawingCache())之後
         *
         * @param itemView
         */
        void afterDrawingCache(View itemView);
    }
}

類中定義了DragItemListener,以便把處理業務的時候操作更加方便。前面圖中的拖拽效果結合DragListView的使用的關鍵代碼為:
mListView.setDragItemListener(new DragListView.DragItemListener() {

    private Rect mFrame = new Rect();
    private boolean mIsSelected;

    @Override
    public boolean onExchange(int srcPosition, int position) {
        boolean result = mAdapter.exchange(srcPosition, position);
        return result;
    }

    @Override
    public void onRelease(int positon) { 
    }

    @Override
    public boolean canDrag(View dragView, int x, int y) {
        // 獲取可拖拽的圖標
        View dragger = dragView.findViewById(R.id.dl_plugin_move);
        if (dragger == null || dragger.getVisibility() != View.VISIBLE) {
            return false;
        }
        float tx = x - ViewUtil.getX(dragView);
        float ty = y - ViewUtil.getY(dragView);
        dragger.getHitRect(mFrame);
        if (mFrame.contains((int) tx, (int) ty)) { // 當點擊拖拽圖標才可進行拖拽
            return true;
        }
        return false;
    }

    @Override
    public void startDrag(int position) { 
    }

    @Override
    public void beforeDrawingCache(View dragView) {
        mIsSelected = dragView.isSelected();
        View drag = dragView.findViewById(R.id.dl_plugin_move);
        dragView.setSelected(true);
        if (drag != null) {
            drag.setSelected(true);
        }
    }

    @Override
    public void afterDrawingCache(View dragView) {
        dragView.setSelected(mIsSelected);
        View drag = dragView.findViewById(R.id.dl_plugin_move);
        if (drag != null) {
            drag.setSelected(false);
        }
    }
});


實踐中會不斷的改進的代碼,請大家關注最新完整的代碼:Androids" target="_blank">https://github.com/1993hzw/Androids

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved