基于ViewDragHelper的抽屉控件

现在android的抽屉控件很多,android官方也有,但是不符合我想要的样式,于是

自己写了一个。我想要的结果是滑动的时候抽屉并不动,而是首页动,android官方的

抽屉是首页不动,是将抽屉滑到首页上,qq的样式为首页滑动,漏出抽屉,但是抽屉也

是跟随着首页的滑动而滑出来的。

ViewDragHelper可以不接触ontouch而控制子view的移动,用来做抽屉也很简单很方便很强大。这个实现我的要求,有其他的需求改动起来也很方便。

首先,要实例一个ViewDragHelper。

ViewDragHelper.create(this, 1.0f, mCallback);

其中的mCallback是ViewDragHelper.Callback实例。

private class DrawerCallbak extends ViewDragHelper.Callback {}

自定义的ViewDragHelper.Callback类。

通过这个Callback就可以控制子view的位置移动了。很神奇。这个类中有很多回调。

@Override
public boolean tryCaptureView(View child, int pointerId) {
    return true;
}
这个回调决定了哪个view是可移动的,return true可以移动,return false不可以移动。可以根据参数种的child来对子view进行可不可以移动的设置。



@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
    return Math.max(Math.min(mDefaultslideWidth, left), 0);
}
这个回调了决定了当前view的水平移动位置,child是子view实例,left是当前将要移动位置,dx是即将移动的位置相对于当前的移动距离。return的为移动的位置,例如当前位置x坐标200,手指向右拖动,回调返回了dx为5像素,那么left的值为205,如果return 205,那么将按手指的滑动移动。如果返回300就会像右跳动。


@Override
public int clampViewPositionVertical(View child, int top, int dy) {
    return 0;
}
这个回调决定了当前view的竖直可移动范围,跟上面的水平用法类似。


@Override
public int getViewHorizontalDragRange(View child) {
    return Math.max(Math.min(mDefaultslideWidth, child.getLeft()), 0);
}

@Override
public int getViewVerticalDragRange(View child) {
    return super.getViewVerticalDragRange(child);
}
这两个跟上面两个差不多,如果被移动的view没有获取焦点的话,不用重写这个两个,但是如果上面的view有焦点,发现移动不了的情况,要重写这两个,跟上面也类似。我现在的view就是水平可移动,竖直不可移动。


@Override
 public void onViewDragStateChanged(int state) {}
这个回调是当view的状态改变的时候会回调,一共有三种状态。

ViewDragHelper.STATE_DRAGGING             拖动
ViewDragHelper.STATE_SETTLING             自动滑动
ViewDragHelper.STATE_IDLE                 停止
 
 
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
      super.onViewPositionChanged(changedView, left, top, dx, dy);
}
这个回调是view位置发生变化是触发,changedView是发生变化滑动view,left和top分别是当前view左上角x坐标和y坐标,dx和dy为相对于之前位置偏移量。


@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
       mDragger.settleCapturedViewAt(0, 0);
}
这个回调为当拖动停止,手离开屏幕的时候被回调,可以通过这个回调让滑到一半的view继续滑动,这是抽屉必不可少的,settleCapturedViewAt方法可以设置view最终目的地的位置坐标,这个方法必须在callback回调中调用,但是抽屉控件都会提供方法来控制抽屉的打开关闭,在view的方法中直接调用这个方法是会崩溃的。

mDragger.smoothSlideViewTo(mContentView, 0, 0);
这个方法也能控制view移动,可以在view的方法中直接调用。



下面的三个回调方法是view的,不是viewDraghelper的,前两个是分配touch事件的,都交给viewDraghelper来处理了,第三个跟上面的回调有关,要重写这个方法才能使view自动滑动生效。
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        return mDragger.shouldInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragger.processTouchEvent(event);
        return true;
    }


    @Override
    public void computeScroll() {
        if (mDragger.continueSettling(true)) {
            invalidate();
        }
    }

合理利用上面提到的回调方法就可以写出抽屉控件了,下面是示例代码。


public class DrawerLayout extends FrameLayout {
    private ViewDragHelper mDragger;
    private ViewDragHelper.Callback mCallback;
    private int mDefaultslideWidth;
    private boolean mIsOpen = false;
    private View mContentView;
    private boolean mIsScrolled = false;
    private boolean mIsAutoScrolled = false;
    private OnStateChangedListener mOnStateChangedListener;

    public DrawerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        mCallback = new DrawerCallbak();
        mDragger = ViewDragHelper.create(this, 1.0f, mCallback);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mContentView = getChildAt(1);
        if (mContentView == null) {
            throw new NullPointerException("contentview is null");
        }

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (getChildAt(0) != null) {
            mDefaultslideWidth = getChildAt(0).getWidth();
        } else {
            try {
                mDefaultslideWidth = getChildAt(0).getWidth();
            } catch (NullPointerException e) {
                Log.e("DrawerLayout", "Layout has at least one child view!");
            }

        }
    }

    private class DrawerCallbak extends ViewDragHelper.Callback {


        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            if (mIsAutoScrolled) {
                return false;
            }
            return child == mContentView;
        }

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            return Math.max(Math.min(mDefaultslideWidth, left), 0);
        }

        @Override
        public int getViewHorizontalDragRange(View child) {
            return Math.max(Math.min(mDefaultslideWidth, child.getLeft()), 0);
        }

        @Override
        public int getViewVerticalDragRange(View child) {
            return super.getViewVerticalDragRange(child);
        }

        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            return 0;
        }

        @Override
        public void onViewDragStateChanged(int state) {
            switch (state) {
                case ViewDragHelper.STATE_DRAGGING:
                    mIsScrolled = true;
                    break;
                case ViewDragHelper.STATE_IDLE:
                    mIsAutoScrolled = false;
                    mIsScrolled = false;
                    if (mContentView.getLeft() == 0) {
                        mIsOpen = false;
                    } else {
                        mIsOpen = true;
                    }
                    if (mOnStateChangedListener != null) {
                        if (mIsOpen) {
                            mOnStateChangedListener.onOpen(mContentView);
                        } else {
                            mOnStateChangedListener.onClosed(mContentView);
                        }
                    }
                    break;
                case ViewDragHelper.STATE_SETTLING:
                    mIsAutoScrolled = true;
                    break;

            }
        }

        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            if (changedView == mContentView) {
                if (mOnStateChangedListener != null) {
                    mOnStateChangedListener.onScrolled(mContentView, (int) (((float) left / (float) mDefaultslideWidth) * 100));
                }
            }
            super.onViewPositionChanged(changedView, left, top, dx, dy);
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            if (mIsAutoScrolled) {
                return;
            }
            if (releasedChild == mContentView) {
                if (mIsScrolled) {
                    if (xvel <= 0) {
                        mDragger.settleCapturedViewAt(0, 0);
                    } else {
                        mDragger.settleCapturedViewAt(mDefaultslideWidth, 0);
                    }
                } else if (mIsOpen) {
                    if (xvel <= 0) {
                        mDragger.settleCapturedViewAt(0, 0);
                    }
                } else {
                    if (xvel > 0) {
                        mDragger.settleCapturedViewAt(mDefaultslideWidth, 0);
                    }
                }
                invalidate();
            } else {
                super.onViewReleased(releasedChild, xvel, yvel);
            }
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        return mDragger.shouldInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragger.processTouchEvent(event);
        return true;
    }


    @Override
    public void computeScroll() {
        if (mDragger.continueSettling(true)) {
            invalidate();
        }
    }

    public void open() {
        if (!mIsOpen) {
            controlView();
        }
    }

    public void close() {
        if (mIsOpen) {
            controlView();
        }
    }

    public void controlView() {
        if (mIsScrolled || mIsAutoScrolled) {
            return;
        }
        if (mIsOpen) {
            mDragger.smoothSlideViewTo(mContentView, 0, 0);
        } else {
            mDragger.smoothSlideViewTo(mContentView, mDefaultslideWidth, 0);
        }
        invalidate();
    }

    public interface OnStateChangedListener {
        void onOpen(View view);

        void onClosed(View view);

        void onScrolled(View view, int percentage);
    }

    public void setOnStateChangedListener(OnStateChangedListener onStateChangedListener) {
        mOnStateChangedListener = onStateChangedListener;
    }

    public boolean isOpen() {
        return mIsOpen;
    }


}







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值