现在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的方法中直接调用。
@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;
}
}