item可拖拽的GridView實現

前言

之前的項目中有個類似網易新聞的標簽排序功能.長按某個標簽后可拖動進行排序,當時用GridView實現的,今天復習總結,記錄一下.

概述

這個效果實現起來并不難,我先說一下大體思路,隨后附上代碼.創建一個當前item的鏡像view,并隱藏當前item,在move時更新view的layoutParams來改變鏡像view的位置,同時判斷當前位置的pos,如果有變化則隱藏當前位置item,讓之前隱藏的item顯示,并通過回調接口(自定義的)來通知外部更新數據源,刷新gridView.在up或cancel時顯示出隱藏的item,并釋放鏡像view.

public class DragGridView extends GridView {

    /**
     * dragGridView item長按的響應時間 默認1000毫秒,可以自定義
     */
    private long mLongItemResponeTime = 1000;

    /**
     * 是否可以拖拽,默認不可以
     */
    private boolean isDrag = false;

    /**
     * 正在拖拽的item的position
     */
    private int mDragPosition;

    /**
     * 開始拖動item的view對象
     */
    private View mStartDragItemView = null;

    /**
     * 鏡像View
     */
    private ImageView mDragImageView;

    /**
     * 震動器
     */
    private Vibrator mVibrator;

    /**
     * windowManager 通過其添加鏡像view到當前窗口
     */
    private WindowManager mWindowManager;

    /**
     * 鏡像view的layoutParams
     */
    private WindowManager.LayoutParams mLayoutParams;

    /**
     * 拖動item緩存的bitmap
     */
    private Bitmap mDragBitmap;

    /**
     * 按下點到item的上邊距
     */
    private int mPoint2ItemTop;

    /**
     * 按下的點到item的左邊距
     */
    private int mPoint2ItemLeft;

    /**
     * gridView距離屏幕的上邊距
     */
    private int mOffset2Top;

    /**
     * gridView距離屏幕的左邊距
     */
    private int mOffset2Left;

    /**
     * 狀態欄的高度
     */
    private int mStatusHeight;

    /**
     * 為外部提供的item拖動位置改變的回調接口
     */
    private OnChangeListener mOnChangeListener;

    private Handler mHandler = new Handler();

    private int mDownX;
    private int mDownY;
    private int mMoveX;
    private int mMoveY;


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

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

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

        //獲取振動器
        mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
        //獲取windowManager
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        //獲取狀態欄高度
        mStatusHeight = getStatusHeight(context);
    }

    /**
     * 設置長按item的時間
     *
     * @param time 默認1000毫秒
     */
    public void setLongItemResponeTime(int time) {
        mLongItemResponeTime = time;
    }

    /**
     * 設置item狀態改變的監聽對象,主要用于處理外部數據的交換
     *
     * @param onChangeListener
     */
    public void setOnChangeListener(OnChangeListener onChangeListener) {
        mOnChangeListener = onChangeListener;
    }

    /**
     * 長按item后的任務
     */
    private Runnable mLongClickRun = new Runnable() {
        @Override
        public void run() {
            //設置可拖動
            isDrag = true;
            //設置震動
            mVibrator.vibrate(50);
            //設置拖拽的item隱藏
            mStartDragItemView.setVisibility(INVISIBLE);

            //根據我們按下的點顯示item鏡像
            createDragImage(mDragBitmap, mDownX, mDownY);
        }
    };

    /**
     * 創建拖動的鏡像
     *
     * @param dragBitmap 按下的item bitmapCache
     * @param downX      按下的點相對父控件的坐標
     * @param downY
     */
    private void createDragImage(Bitmap dragBitmap, int downX, int downY) {
        mLayoutParams = new WindowManager.LayoutParams();
        mLayoutParams.format = PixelFormat.TRANSLUCENT;            //圖片之外其他地方透明
        mLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
        mLayoutParams.x = downX - mPoint2ItemLeft + mOffset2Left;   //設置imageView的原點
        mLayoutParams.y = downY - mPoint2ItemTop + mOffset2Top;
        mLayoutParams.alpha = 0.55f;                                //設置透明度
        mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;

        mDragImageView = new ImageView(getContext());
        mDragImageView.setImageBitmap(dragBitmap);
        mWindowManager.addView(mDragImageView, mLayoutParams);   //添加該iamgeView到window
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:

                //獲取按下時的坐標
                mDownX = (int) event.getX();
                mDownY = (int) event.getY();

                //根據坐標獲取所點擊的item的position
                mDragPosition = pointToPosition(mDownX, mDownY);

                if (mDragPosition == AdapterView.INVALID_POSITION)
                    return super.dispatchHoverEvent(event);

                //提交延遲任務到handler    在抬起時清空handler如果在延遲時間內抬起,任務還沒執行就已經被清除了
                mHandler.postDelayed(mLongClickRun, mLongItemResponeTime);

                //根據position獲取該item對應的View  獲取viewGroup中的子View
                mStartDragItemView = getChildAt(mDragPosition - getFirstVisiblePosition());

                //獲取按下的點距離item的邊距
                mPoint2ItemTop = mDownY - mStartDragItemView.getTop();
                mPoint2ItemLeft = mDownX - mStartDragItemView.getLeft();

                //根據按下的點到屏幕邊緣的距離減去該點到控件邊緣的距離得出控件的邊距
                mOffset2Top = (int) (event.getRawY() - mDownY);
                mOffset2Left = (int) (event.getRawX() - mDownX);

                //開啟選中item的繪圖緩存
                mStartDragItemView.setDrawingCacheEnabled(true);
                //獲取item緩存的bitmap對象
                mDragBitmap = Bitmap.createBitmap(mStartDragItemView.getDrawingCache());
                //釋放繪圖緩存,避免出現重復鏡像
                mStartDragItemView.destroyDrawingCache();

                Log.e("xinyang", "dispatchTouchEvent ------- down");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e("xinyang", "dispatchTouchEvent ------- move");
                break;
            case MotionEvent.ACTION_UP:
                mHandler.removeCallbacks(mLongClickRun);
                Log.e("xinyang", "dispatchTouchEvent ------- up");
                break;
            case MotionEvent.ACTION_CANCEL:
                Log.e("xinyang", "dispatchTouchEvent ------- cancel");
                mHandler.removeCallbacks(mLongClickRun);
                break;
        }
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        //判斷是否可以拖動
        if (isDrag && mDragImageView != null) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.e("xinyang", "onTouchEvent ------- down");
                    break;
                case MotionEvent.ACTION_MOVE:

                    Log.e("xinyang", "onTouchEvent ------- move");

                    mMoveX = (int) ev.getX();
                    mMoveY = (int) ev.getY();

                    //更新鏡像view位置
                    onDragItem(mMoveX, mMoveY);
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e("xinyang", "onTouchEvent ------- up");
                    isDrag = false;
                    onStopDrag();
                    break;
            }
            return true;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 停止拖動
     */
    private void onStopDrag() {
        View view = getChildAt(mDragPosition - getFirstVisiblePosition());
        if (view != null) {
            view.setVisibility(View.VISIBLE);
        }
        removeImage();
    }

    /**
     * 拖動完成時移除掉imageView
     */
    private void removeImage() {
        if (mDragImageView != null && mWindowManager != null) {
            mWindowManager.removeView(mDragImageView);
            mDragImageView = null;
        }
    }

    /**
     * 拖動item   使用updateViewLayout方法來改變imageView的位置
     *
     * @param moveX
     * @param moveY
     */
    private void onDragItem(int moveX, int moveY) {

        if (mLayoutParams == null || mDragImageView == null) {
            return;
        }

        mLayoutParams.x = moveX - mPoint2ItemLeft + mOffset2Left;
        mLayoutParams.y = moveY - mPoint2ItemTop + mOffset2Top;
        mWindowManager.updateViewLayout(mDragImageView, mLayoutParams);

        onSwapItem(moveX, moveY);
    }


    /**
     * 交換item
     *
     * @param moveX
     * @param moveY
     */
    private void onSwapItem(int moveX, int moveY) {
        //當前移動到的位置
        int tempPosition = pointToPosition(moveX, moveY);

        if (tempPosition != mDragPosition && tempPosition != AdapterView.INVALID_POSITION) {

            if (mOnChangeListener != null) {
                mOnChangeListener.onChange(mDragPosition, tempPosition);
            }

            //實際上在外面的onChange實現中做了數據交換并且刷新了gridView 這里做的只是讓新的位置看起來沒有內容而已!
            getChildAt(tempPosition - getFirstVisiblePosition()).setVisibility(INVISIBLE);
            getChildAt(mDragPosition - getFirstVisiblePosition()).setVisibility(VISIBLE);

            mDragPosition = tempPosition;
        }
    }

    /**
     * 獲取狀態欄的高度
     *
     * @param context
     * @return
     */
    private static int getStatusHeight(Context context) {
        int statusHeight = 0;
        Rect localRect = new Rect();
        ((Activity) context).getWindow().getDecorView().getWindowVisibleDisplayFrame(localRect);
        statusHeight = localRect.top;
        if (0 == statusHeight) {
            Class<?> localClass;
            try {
                localClass = Class.forName("com.android.internal.R$dimen");
                Object localObject = localClass.newInstance();
                int i5 = Integer.parseInt(localClass.getField("status_bar_height").get(localObject).toString());
                statusHeight = context.getResources().getDimensionPixelSize(i5);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return statusHeight;
    }

    /**
     * item狀態改變的監聽
     */
    public interface OnChangeListener {

        /**
         * 當item交換位置時回調的方法,我們只需要在這個方法中實現數據的交換即可
         *
         * @param from 開始item的position
         * @param to   拖拽到的item的position
         */
        void onChange(int from, int to);

    }
}

在activity中交換數據的代碼:

mDragGridView.setOnChangeListener(new DragGridView.OnChangeListener() {
            @Override
            public void onChange(int from, int to) {
                if (from < to) {
                    for (int i = from; i < to; i++) {
                        Collections.swap(dataSourceList, i, i + 1);
                    }
                }else if (from > to) {
                    for (int i = from; i > to; i--) {
                        Collections.swap(dataSourceList, i, i - 1);
                    }
                }
                mSimpleAdapter.notifyDataSetChanged();
            }
        });

然后就OK啦!

如果用recyclerView實現的話應該是更方便,recyclerView里面有一些列的notifyItemChange方法,且帶有動畫特效.改天用recyclerView弄一下試試!

菜雞一枚,如有不正確的地方還望dalao們指正!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,593評論 25 708
  • 內容抽屜菜單ListViewWebViewSwitchButton按鈕點贊按鈕進度條TabLayout圖標下拉刷新...
    皇小弟閱讀 46,907評論 22 665
  • 故障: 車輛熱車后在熄火不好熄火,且故障燈常亮 診斷: 1、電腦查看故障碼,故障為燃油調整濃,此車一般報此碼都是碳...
    宏宇_8a57閱讀 1,172評論 0 1
  • 風雨交加的夜晚常常讓人心亂不寧, 大地遁入黑暗,世界仿佛變得扭曲。沒有了往日的清晰,給人一種極不真實的感覺。在黑暗...
    威廉子爵閱讀 927評論 2 2
  • 在GAN的相關研究如火如荼甚至可以說是泛濫的今天,一篇新鮮出爐的arXiv論文《Wasserstein GAN》卻...
    MiracleJQ閱讀 2,272評論 0 8