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们指正!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,442评论 25 707
  • 内容抽屉菜单ListViewWebViewSwitchButton按钮点赞按钮进度条TabLayout图标下拉刷新...
    皇小弟阅读 46,705评论 22 664
  • 故障: 车辆热车后在熄火不好熄火,且故障灯常亮 诊断: 1、电脑查看故障码,故障为燃油调整浓,此车一般报此码都是碳...
    宏宇_8a57阅读 1,136评论 0 1
  • 风雨交加的夜晚常常让人心乱不宁, 大地遁入黑暗,世界仿佛变得扭曲。没有了往日的清晰,给人一种极不真实的感觉。在黑暗...
    威廉子爵阅读 851评论 2 2
  • 在GAN的相关研究如火如荼甚至可以说是泛滥的今天,一篇新鲜出炉的arXiv论文《Wasserstein GAN》却...
    MiracleJQ阅读 2,218评论 0 8