前言
前面研究了DropTopLayout嵌套滚动固定头部的代码,发现在滑动时(1)不能联动滚动头部,而且实现上感觉也没有较好封装,特别在滚动头部不可见时向下滚动依靠ev.getY()并不能很好的解决嵌套事件反向传递,而是要调用者自己在滑动时touchMode,很蛋疼,于是继续研究了ScrollableLayout这个类库实现
思路
画了个大致流程,不然直接看代码有点懵,整个流程主要围绕着是滑动嵌套滚动区域还是拖动整个页面,以及手指松开将整个事件是否惯性滚动
Paste_Image.png
滚动嵌套区域的条件
-向上滚动时,如果固定栏顶部已经紧靠顶部时
-向下滚动时,嵌套区域还能向下滑动
-手指松开时,当移动的惯性满足条件且满足上面的条件
滑动整个页面的条件
-与上面相反
为什么在dispatchTouchEvent做事件处理
dispatchTouchEvent 方法做事件分发,返回true,能直接拦截所有事件向下传递
为什么重写onMeasure
在滚动整体内容时,top 和 contentView 刚好撑满当前屏幕,在屏幕外的仍然是空白区域,将整块区域向上滚动时,空白区域就显示出来了,所以让contentView 填满占满整块底部区域
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mHeadView = getChildAt(0);
measureChildWithMargins(mHeadView, widthMeasureSpec, 0, MeasureSpec.UNSPECIFIED, 0);
maxY = mHeadView.getMeasuredHeight();
mHeadHeight = mHeadView.getMeasuredHeight();
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec) + maxY, MeasureSpec.EXACTLY));
}
围绕大纲看代码
float currentX = ev.getX();
float currentY = ev.getY();
float deltaY;
int shiftX = (int) Math.abs(currentX - mDownX);
int shiftY = (int) Math.abs(currentY - mDownY);
case MotionEvent.ACTION_DOWN:
mDisallowIntercept = false;
needCheckUpdown = true;
updown = true;
//省略代码 记录初始x y
break;
case MotionEvent.ACTION_MOVE:
if (mDisallowIntercept) {
break;
}
//初始化速度跟踪
deltaY = mLastY - currentY;
if (needCheckUpdown) {
//判断是否是左右滑动
if (shiftX > mTouchSlop && shiftX > shiftY) {
needCheckUpdown = false;
updown = false;
//是否是上下滑动
} else if (shiftY > mTouchSlop && shiftY > shiftX) {
needCheckUpdown = false;
updown = true;
}
}
//如果是上下滑动并且当前当前是嵌套view顶部 就滑动整块View
if (updown && shiftY > mTouchSlop && shiftY > shiftX &&
(!isSticked() || mHelper.isTop() || isClickHeadExpand)) {
//禁用ViewPager的滑动防止 在滑动时 横向滑动时滑动到ViewPager
if (childViewPager != null) {
Log.e("test","requestDisallowInterceptTouchEvent");
childViewPager.requestDisallowInterceptTouchEvent(true);
}
//然后滑动整个view
scrollBy(0, (int) (deltaY + 0.5));
}
mLastY = currentY;
break;
手指松开时
case MotionEvent.ACTION_UP:
//当手指松开
if (updown && shiftY > shiftX && shiftY > mTouchSlop) {
//计算1000毫秒速率
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
float yVelocity = -mVelocityTracker.getYVelocity();
boolean dislowChild = false;
//速率满足条件
if (Math.abs(yVelocity) > mMinimumVelocity) {
mDirection = yVelocity > 0 ? DIRECTION.UP : DIRECTION.DOWN;
//如果向上滑动并且滑动到离顶部最近的
if ((mDirection == DIRECTION.UP && isSticked()) || (!isSticked() && getScrollY() == 0 && mDirection == DIRECTION.DOWN)) {
dislowChild = true;
} else {
//否则滑动到最大距离
mScroller.fling(0, getScrollY(), 0, (int) yVelocity, 0, 0, -Integer.MAX_VALUE, Integer.MAX_VALUE);
mScroller.computeScrollOffset();
mLastScrollerY = getScrollY();
invalidate();
}
}
//否则把事件传递给子类
if (!dislowChild && (isClickHead || !isSticked())) {
int action = ev.getAction();
ev.setAction(MotionEvent.ACTION_CANCEL);
boolean dispathResult = super.dispatchTouchEvent(ev);
Log.e("test","super.dispatchTouchEvent(ev)");
ev.setAction(action);
return dispathResult;
}
}
惯性滑动
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
final int currY = mScroller.getCurrY();
if (mDirection == DIRECTION.UP) {
// 手势向上划
if (isSticked()) {
int distance = mScroller.getFinalY() - currY;
int duration = calcDuration(mScroller.getDuration(), mScroller.timePassed());
mHelper.smoothScrollBy(getScrollerVelocity(distance, duration), distance, duration);
mScroller.forceFinished(true);
return;
} else {
scrollTo(0, currY);
}
} else {
// 手势向下划
if (mHelper.isTop() || isClickHeadExpand) {
int deltaY = (currY - mLastScrollerY);
int toY = getScrollY() + deltaY;
scrollTo(0, toY);
if (mCurY <= minY) {
mScroller.forceFinished(true);
return;
}
}
invalidate();
}
mLastScrollerY = currY;
}
}
重写scrollBy scrollTo方法 防止滑出范围
@Override
public void scrollBy(int x, int y) {
int scrollY = getScrollY();
int toY = scrollY + y;
if (toY >= maxY) {
toY = maxY;
} else if (toY <= minY) {
toY = minY;
}
y = toY - scrollY;
super.scrollBy(x, y);
}
@Override
public void scrollTo(int x, int y) {
if (y >= maxY) {
y = maxY;
} else if (y <= minY) {
y = minY;
}
mCurY = y;
if (onScrollListener != null) {
onScrollListener.onScroll(y, maxY);
}
super.scrollTo(x, y);
}