zoukankan      html  css  js  c++  java
  • android 自定义View开发实战(六) 可拖动的GridView

    1前言

    由于项目需求,需要把项目的主界面采用GridView显示,并且需要根据模块优先级支持拖动图标(砍死产品狗)。为此,自定义了一个支持拖拽图标的GridView。效果如下:
    这里写图片描述

    具体效果如上图

    2 可拖拽的GridView实现

    要实现上面的效果有两个难点,第一就是如何创造一个可拖动的View在我们的Activity界面上。第二个就是如何实现两个View的交换

    关于第一个:我们可以用WindowManager 来往我们的界面上添加View,这样我们再重写GridView的onTouchEvent()方法,根据移动的距离来更新的我们View的位置即可
    关于第二个:可以这样实现,当我们拖动时,创建一个透明度低一点的镜像item View。把要拖动的item对应的View先隐藏起来,此时Adapter的item先不交换,当我们把拖动的item移动到另一个item对应的范围内,我们再进行交换,先把这个item隐藏掉,然后在原来的位置显示出这个item。最后镜像item对应的view 再消失。

    其实关于第二点,也有其他的交换策略,比如判断拖到镜像view到另一个item之上一段时间再进行交换等。

    1 实现思路

    好了,下面我们仔细总结了一下思路,有了思路我们就很好办了:

    1.根据手指按下的X,Y坐标来获取我们在GridView上面点击的item,再获取对应的View

    2.手指按下的时候使用Handler和Runnable来实现一个定时器,假如定时时间为1000毫秒,在1000毫秒内,如果手指抬起了就移除定时器,没有抬起并且手指点击在GridView的item所在的区域,则表示我们长按了GridView的item

    3. 如果我们长按了item则隐藏item,然后使用WindowManager来添加一个item的镜像在屏幕用来代替刚刚隐藏的item

    4.当我们手指在屏幕移动的时候,更新item镜像的位置,然后在根据我们移动的X,Y的坐标来获取移动到GridView的哪一个位置

    5. 到GridView的item过多的时候,可能一屏幕显示不完,我们手指拖动item镜像到屏幕下方,要触发GridView想上滚动,同理,当我们手指拖动item镜像到屏幕上面,触发GridView向下滚动

    6.GridView交换数据,刷新界面,移除item的镜像

    2 实现代码

    这里定义一个XGridView继承GridView来实现
    代码如下,加了详细的注释,应该容易看懂

    package com.qiyei.javatest;
    
    import android.app.Activity;
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.PixelFormat;
    import android.graphics.Rect;
    import android.os.Handler;
    import android.os.Vibrator;
    import android.util.AttributeSet;
    import android.view.Gravity;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.WindowManager;
    import android.widget.GridView;
    import android.widget.ImageView;
    
    /**
     * Created by qiyei2015 on 2017/1/5.
     */
    public class XGridView extends GridView {
    
        //拖拽响应的时间 默认为1s
        private long mDragResponseMs = 1000;
        //是否支持拖拽,默认不支持
        private boolean isDrag = false;
        //振动器,用于提示替换
        private Vibrator mVibrator;
        //拖拽的item的position
        private int mDragPosition;
        //拖拽的item对应的View
        private View mDragView;
    
        //窗口管理器,用于为Activity上添加拖拽的View
        private WindowManager mWindowManager;
        //item镜像的布局参数
        private WindowManager.LayoutParams mLayoutParams;
    
        //item镜像的 显示镜像,这里用ImageView显示
        private ImageView mDragMirrorView;
        //item镜像的bitmap
        private Bitmap mDragBitmap;
    
        //按下的点到所在item的左边缘距离
        private int mPoint2ItemLeft;
        private int mPoint2ItemTop;
    
        //DragView到上边缘的距离
        private int mOffset2Top;
        private int mOffset2Left;
    
        //按下时x,y
        private int mDownX;
        private int mDownY;
        //移动的时x.y
        private int mMoveX;
        private int mMoveY;
    
        //状态栏高度
        private int mStatusHeight;
    
        //XGridView向下滚动的边界值
        private int mDownScrollBorder;
        //XGridView向上滚动的边界值
        private int mUpScrollBorder;
        //滚动的速度
        private int mSpeed = 20;
    
        //item发生变化的回调接口
        private OnItemChangeListener changeListener;
    
        private Handler mHandler;
    
        /**
         * 长按的Runnable
         */
        private Runnable mLongClickRunable = new Runnable() {
            @Override
            public void run() {
    
                isDrag = true;
                mVibrator.vibrate(200);
                //隐藏该item
                mDragView.setVisibility(INVISIBLE);
    
                //在点击的地方创建并显示item镜像
                createDragView(mDragBitmap,mDownX,mDownY);
            }
        };
    
        /**
         * 当moveY的值大于向上滚动的边界值,触发GridView自动向上滚动
         * 当moveY的值小于向下滚动的边界值,触犯GridView自动向下滚动
         * 否则不进行滚动
         */
        private Runnable mScrollRunbale = new Runnable() {
            @Override
            public void run() {
                int scrollY = 0;
                if (mMoveY > mUpScrollBorder){
                    scrollY = mSpeed;
                    mHandler.postDelayed(mScrollRunbale,25);
                }else if (mMoveY < mDownScrollBorder){
                    scrollY = -mSpeed;
                    mHandler.postDelayed(mScrollRunbale,25);
                }else {
                    scrollY = 0;
                    mHandler.removeCallbacks(mScrollRunbale);
                }
                smoothScrollBy(scrollY,10);
            }
        };
    
    
        public XGridView(Context context) {
            this(context,null);
        }
    
        public XGridView(Context context, AttributeSet attrs) {
            this(context, attrs,0);
        }
    
        public XGridView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
    
            mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            mHandler = new Handler();
            mStatusHeight = getStatusHeight(context);
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            switch (ev.getAction()){
                case MotionEvent.ACTION_DOWN:
                    mDownX = (int)ev.getX();
                    mDownY = (int)ev.getY();
    
                    //获取按下的position
                    mDragPosition = pointToPosition(mDownX,mDownY);
                    if (mDragPosition == INVALID_POSITION){     //无效就返回
                        return super.dispatchTouchEvent(ev);
                    }
    
                    //延时长按执行mLongClickRunable
                    mHandler.postDelayed(mLongClickRunable,mDragResponseMs);
                    //获取按下的item对应的View 由于存在复用机制,所以需要 处理FirstVisiblePosition
                    mDragView = getChildAt(mDragPosition - getFirstVisiblePosition());
                    if (mDragView == null){
                        return super.dispatchTouchEvent(ev);
                    }
    
                    //计算按下的点到所在item的left top 距离
                    mPoint2ItemLeft = mDownX - mDragView.getLeft();
                    mPoint2ItemTop = mDownY - mDragView.getTop();
                    //计算GridView的left top 偏移量:原始距离 - 相对距离就是偏移量
                    mOffset2Left = (int)ev.getRawX() - mDownX;
                    mOffset2Top = (int)ev.getRawY() - mDownY;
    
                    //获取GridView自动向下滚动的偏移量,小于这个值,DragGridView向下滚动
                    mDownScrollBorder = getHeight() /4;
                    //获取GridView自动向上滚动的偏移量,大于这个值,DragGridView向上滚动
                    mUpScrollBorder = getHeight() * 3/4;
    
                    //开启视图缓存
                    mDragView.setDrawingCacheEnabled(true);
                    //获取缓存的中的bitmap镜像 包含了item中的ImageView和TextView
                    mDragBitmap = Bitmap.createBitmap(mDragView.getDrawingCache());
                    //释放视图缓存 避免出现重复的镜像
                    mDragView.destroyDrawingCache();
    
                    break;
                case MotionEvent.ACTION_MOVE:
    
                    mMoveX = (int)ev.getX();
                    mMoveY = (int)ev.getY();
    
                    //如果只在按下的item上移动,未超过边界,就不移除mLongClickRunable
                    if (!isTouchInItem(mDragView,mMoveX,mMoveY)){
                        mHandler.removeCallbacks(mLongClickRunable);
                    }
    
                    break;
                case MotionEvent.ACTION_UP:
                    mHandler.removeCallbacks(mLongClickRunable);
                    mHandler.removeCallbacks(mScrollRunbale);
                    break;
                default:
                    break;
            }
            return super.dispatchTouchEvent(ev);
        }
    
    
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            if (isDrag && mDragMirrorView != null){
                switch (ev.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        break;
                    case MotionEvent.ACTION_MOVE:
                        mMoveX = (int)ev.getX();
                        mMoveY = (int)ev.getY();
                        onDragItem(mMoveX,mMoveY);
                        break;
                    case MotionEvent.ACTION_UP:
                        onStopDrag();
                        isDrag = false;
                        break;
                    default:
                        break;
                }
                return true;
            }
            return super.onTouchEvent(ev);
        }
    
    
        /************************对外提供的接口***************************************/
    
        public boolean isDrag() {
            return isDrag;
        }
    
        public void setDrag(boolean drag) {
            isDrag = drag;
        }
    
        public long getDragResponseMs() {
            return mDragResponseMs;
        }
    
        public void setDragResponseMs(long mDragResponseMs) {
            this.mDragResponseMs = mDragResponseMs;
        }
    
        public void setOnItemChangeListener(OnItemChangeListener changeListener) {
            this.changeListener = changeListener;
        }
        /******************************************************************************/
    
    
        /**
         * 点是否在该View上面
         * @param view
         * @param x
         * @param y
         * @return
         */
        private boolean isTouchInItem(View view, int x, int y) {
            if (view == null){
                return false;
            }
            if (view.getLeft() < x  && x < view.getRight()
                    && view.getTop() < y && y < view.getBottom()){
                return true;
            }else {
                return false;
            }
        }
    
    
        /**
         * 获取状态栏的高度
         * @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 height = Integer.parseInt(localClass.getField("status_bar_height").get(localObject).toString());
                    statusHeight = context.getResources().getDimensionPixelSize(height);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return statusHeight;
        }
    
        /**
         * 停止拖动
         */
        private void onStopDrag() {
            View view = getChildAt(mDragPosition - getFirstVisiblePosition());
            if (view != null){
                view.setVisibility(VISIBLE);
            }
            removeDragImage();
        }
    
        /**
         * WindowManager 移除镜像
         */
        private void removeDragImage() {
            if (mDragMirrorView != null){
                mWindowManager.removeView(mDragMirrorView);
                mDragMirrorView = null;
            }
        }
    
        /**
         * 拖动item到指定位置
         * @param x
         * @param y
         */
        private void onDragItem(int x, int y) {
            mLayoutParams.x = x - mPoint2ItemLeft + mOffset2Left;
            mLayoutParams.y = y - mPoint2ItemTop + mOffset2Top - mStatusHeight;
            //更新镜像位置
            mWindowManager.updateViewLayout(mDragMirrorView,mLayoutParams);
    
            onSwapItem(x,y);
    
            mHandler.post(mScrollRunbale);
        }
    
        /**
         * 交换 item 并且控制 item之间的显示与隐藏
         * @param x
         * @param y
         */
        private void onSwapItem(int x, int y) {
            //获取我们手指移动到那个item
            int tmpPosition = pointToPosition(x,y);
            if (tmpPosition != INVALID_POSITION && tmpPosition != mDragPosition){
                if (changeListener != null){
                    changeListener.onChange(mDragPosition,tmpPosition);
                }
                //隐藏tmpPosition
                getChildAt(tmpPosition - getFirstVisiblePosition()).setVisibility(INVISIBLE);
                //显示之前的item
                getChildAt(mDragPosition - getFirstVisiblePosition()).setVisibility(VISIBLE);
    
                mDragPosition = tmpPosition;
            }
    
        }
    
        /**
         * 创建拖动的镜像
         * @param bitmap
         * @param downX
         * @param downY
         */
        private void createDragView(Bitmap bitmap, int downX, int downY) {
            mLayoutParams = new WindowManager.LayoutParams();
            mLayoutParams.format = PixelFormat.TRANSLUCENT; //图片之外其他地方透明
            mLayoutParams.gravity = Gravity.TOP | Gravity.LEFT; //左 上
            //指定位置 其实就是 该 item 对应的 rawX rawY 因为Window 添加View是需要知道 raw x ,y的
            mLayoutParams.x = mOffset2Left + (downX - mPoint2ItemLeft);
            mLayoutParams.y = mOffset2Top + (downY - mPoint2ItemTop) + mStatusHeight;
            //指定布局大小
            mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
            mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
            //透明度
            mLayoutParams.alpha = 0.4f;
            //指定标志 不能获取焦点和触摸
            mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
    
            mDragMirrorView = new ImageView(getContext());
            mDragMirrorView.setImageBitmap(bitmap);
            //添加View到窗口中
            mWindowManager.addView(mDragMirrorView,mLayoutParams);
        }
    
    
        /**
         * item 交换时的回调接口
         */
        public interface OnItemChangeListener{
            void onChange(int from,int to);
        }
  • 相关阅读:
    玩儿了一下django User authentication
    python装饰器方法
    django session
    采用asyncore进行实时同步
    django internal search
    素质拓展
    框架,公共模块,unified思想
    Linux操作系统分析__破解操作系统的奥秘
    ASP.NET]ASP.NET C# 用 Print2Flash 实现在线预览 Word、PPT、PDF 等
    [Asp.net]常见word,excel,ppt,pdf在线预览方案,有图有真相,总有一款适合你!
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/8797124.html
Copyright © 2011-2022 走看看