这个我是根据黑马视频做的,用来总结一下。抽屉用到的很多,下面写的比较麻烦,需要读者有耐心的看完。写的不对的地方请提出来。
抽屉我继承的是FrameLayout,因为他有测量的过程,省略了我手动测量。它最重要的就是实现ViewDragHelper的回调方法。文章最后有下载地址。
首先,我们在自定义的View的构造方法里面初始化ViewDragHelper,他是单例模式创建的,并不是new出来的。定义成全局的。
`viewDragHelper = ViewDragHelper.create(this, mCallBack);`
然后就是重写他的回调方法。回调方法,我都是按他们调用的顺序排好了。
@Override
public boolean tryCaptureView(View child, int pointerId) {
//尝试拖拽当前View,如果不能拖拽,就不执行之后的方法
return true;
}
child是当前触摸的View,pointerId是区分多点触摸的Id(没什么用)。注释里面写的有根据返回值判断当前View能不能被拖拽。如果是true,就是不论那个View都能被拖拽。抽屉一般有两个界面。这里就是在一个XML里面写两个平级的Layout就可以了,就是两个界面。比方说,我现在有两个Layout,一个是mMainContent,另一个是mLeftContent。这里如果写成return child==mLeftContent,那就是只有mLeftContent布局能够被拖拽。这里我们就写成return true。后面会有说明。因为你限制只能拖拽mMainContent,当你拖拽mLeftContent的时候,你还想让mMainContent跟着移动。这个时候就需要测量位置,但是如果mLeftContent不能被拖拽,那他就不会执行后面的测量方法。你这里让mLeftContent可以被拖拽,在你拖拽的时候,你重绘界面就可以了,把mLeftContent的位置写死,他就不会移动了,同时也会走后面的测量方法,这样就达到了我们的目的。
//当View被捕获的时候调用
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
}
这个方法是当View被捕获的时候调用,前面一个方法是尝试捕获。如果,你正在尝试捕获的View不能被拖拽,那他就获取不到当前View。
//获取当前View可以横向拖拽的范围
@Override
public int getViewHorizontalDragRange(View child) {
//可拖拽的范围,但是不对拖拽进行限制,仅仅是根据可拖拽的范围计算动画的时长
return mRange;
}
这个方法注释里面有,你拖拽的时候,不可能拖拽到屏幕外面去,如果出现这样的结果,那就是你代码有bug了。这个方法就是避免这样的情况产生。这个范围我写的是0~0.6f*screenWidth。0到0.6倍的当前屏幕宽度。这是横向的,既然有横向的就一定有纵向的。没必要写纵向的。mRange是怎么来的呢?下面这个方法。
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//在onMeasure方法之后调用,并且只有在当前尺寸与之前测量的不相同时调用
mHeight = getMeasuredHeight(); //获取测量的屏幕宽度
mWidth = getMeasuredWidth(); //获取测量的屏幕高度
mRange = (int) (mWidth * 0.6f); //获取可拖拽的横向范围
}
onMeasure方法大家可能都知道,测量宽高的方法,这个方法在onMeasure方法之后调用,并且只有在当前尺寸与之前测量的不相同时调用,就这样,就获取到了mRange。
那最大移动的范围出来了,我们要怎么移动呢?实现下面这个方法
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
//oldLeft:child.getLeft()
//left=oldLeft+dx
if (child == mMainContent) {
left = fixLeft(left);
}
return left;
}
这个方法是:根据建议值修正将要移动到的(横向)位置,此时还没有发生移动。这个方法也有纵向的。child是当前拖拽的View,left是新位置的建议值。dx是位置的变化量。left=oldLeft+dx。oldLeft:child.getLeft()。根据范围修正左边值,我把left的方法提取出来了,后面还有位置用的到。
private int fixLeft(int left) {
if (left < 0) {
return 0;
} else if (left > mRange) {
return mRange;
} else {
return left;
}
}
前面这个方法是还没有移动的时候调用,这个时候屏幕还不会动,当View发生移动的时候调用下面这个方法。
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
int newLeft = left;
if (changedView == mLeftContent) {
newLeft = mMainContent.getLeft() + dx;
}
newLeft = fixLeft(newLeft); //要保证移动的距离不能超过mRange
if (changedView == mLeftContent) {
//mLeftContent保持不变,动态移动mMainContent
mLeftContent.layout(0, 0, 0 + mWidth, 0 + mHeight);
mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 + mHeight);
}
//更新状态,执行动画
dispatchDragEvent(newLeft);
//为了兼容低版本,在每次修改值之后,都要进行重绘
invalidate();
}
移动之后还有一个松开点,这个就是调用下面这个方法。
/**
* 4.当View被释放时,处理的事情(执行动画)
*
* @param releasedChild 被释放的View
* @param xvel 水平方向的速度,向右为正
* @param yvel 垂直方向的速度,向下为正
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if (xvel == 0 && mMainContent.getLeft() > mRange / 2.0f) {
open();
} else if (xvel > 0) {
open();
} else {
close();
}
}
我们需要有一个平滑的移动过程,而不是瞬移。所以,我们要加一个平滑的动画。后面的我也不知道该怎么解释。文字表达能力有限,大家就认真的看完就好了。就是动画效果。是用的github上面一个大牛整理好的。
private void animView(float percent) {
// >1.左面板:缩放动画,平移动画,透明动画
// mLeftContent.setScaleX(0.5f + 0.5f * percent);
// mLeftContent.setScaleY(0.5f + 0.5f * percent);
//缩放动画
ViewHelper.setScaleX(mLeftContent, evaluate(percent, 0.5f, 1.0f));
ViewHelper.setScaleY(mLeftContent, 0.5f + 0.5f * percent);
//平移动画
ViewHelper.setTranslationX(mLeftContent, evaluate(percent, -mWidth / 2.0f, 0));
//透明度动画
ViewHelper.setAlpha(mLeftContent, evaluate(percent, 0.5f, 1.0f));
// >2.主面板:缩放动画1.0->0.8
ViewHelper.setScaleX(mMainContent, evaluate(percent, 1.0f, 0.8f));
ViewHelper.setScaleY(mMainContent, evaluate(percent, 1.0f, 0.8f));
// >3.背景:亮度变化(颜色变化)
// getBackground().setColorFilter((Integer) evaluateColor(percent, Color.YELLOW,Color.BLUE), PorterDuff.Mode.SRC_OVER);
getBackground().setColorFilter((Integer) evaluateColor(percent, Color.BLACK, Color.TRANSPARENT), PorterDuff.Mode.SRC_OVER);
}
/**
* 颜色变化过度
*
* @param fraction
* @param startValue
* @param endValue
* @return
*/
public Object evaluateColor(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
int startA = (startInt >> 24) & 0xff;
int startR = (startInt >> 16) & 0xff;
int startG = (startInt >> 8) & 0xff;
int startB = startInt & 0xff;
int endInt = (Integer) endValue;
int endA = (endInt >> 24) & 0xff;
int endR = (endInt >> 16) & 0xff;
int endG = (endInt >> 8) & 0xff;
int endB = endInt & 0xff;
return (int) ((startA + (int) (fraction * (endA - startA))) << 24) |
(int) ((startR + (int) (fraction * (endR - startR))) << 16) |
(int) ((startG + (int) (fraction * (endG - startG))) << 8) |
(int) ((startB + (int) (fraction * (endB - startB))));
}
/**
* 估值器
*
* @param percentage 百分比
* @param startValue 开始值
* @param endVlaue 结束值
* @return
*/
public Float evaluate(Float percentage, Number startValue, Number endVlaue) {
float startFloat = startValue.floatValue();
return startFloat + percentage * (endVlaue.floatValue() - startFloat);
}
上面这个几个方法就是这样写死,会用就行了。这个我就是copy官方文档里面的。下面这个也是比较重要的,但是不难。
//b.传递触摸事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//传递给viewDragHelper,是否需要拦截触摸事件
return viewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
try {
//处理触摸事件,多点触摸有问题
viewDragHelper.processTouchEvent(event);
} catch (Exception e) {
e.printStackTrace();
}
//返回true,持续接收事件
return true;
}
/**
* 当xml添加完成时调用
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (getChildCount() < 2) {
throw new IllegalStateException("Your ViewGroup must have 2 children at least.");
}
if (!(getChildAt(0) instanceof ViewGroup && getChildAt(1) instanceof ViewGroup)) {
throw new IllegalArgumentException("Your children must be instanceof ViewGroup.");
}
mLeftContent = (ViewGroup) getChildAt(0);
mMainContent = (ViewGroup) getChildAt(1);
}
持续动画的代码
//持续动画
@Override
public void computeScroll() {
super.computeScroll();
if (viewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
算了,不说了,基本上都说了。下面给出下载项目的链接。下载了自己认真瞅瞅就好了。
项目链接