Behavior 研究
研究的Demo地址:https://www.jianshu.com/p/f7989a2a3ec2
这个文章中涉及到了4个Behavior的使用。
关于文章中一点修改的地方:
1、
删除HeaderScrollingViewBehavior#onMeasureChild方法中的逻辑,测试是没有问题的,这段代码中的逻辑主要是重新测量viewpager的高度,防止上滑后底部留白的问题,但实际上是不会存在这个问题的(因为这个viewpager是CoordinatorLayout的子类,默认是与CoordinatorLayout左上角对齐的,只要设置其宽高为match_parent即可)
2、
滑动不灵敏,有时候滑不动,得试两三次
修改viewpager的Behavior中onInterceptTouchEvent方法的逻辑
@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {
//初始状态下,不让viewpager左右滑动,所以水平滑动要拦截,竖直不拦截
//不用拦截竖直滑动事件,因为viewpager并不会消费竖直滑动事件,事件会传递到Recyclerview
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
lastX = ev.getX();
lastY = ev.getY();
}
if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
float currX = ev.getX();
float currY = ev.getY();
float delta=Math.abs(currX - lastX);
boolean horizontalScroll =delta>touchslop;
if (horizontalScroll && child.getTranslationY() == 0)
{
return true;
}
}
return super.onInterceptTouchEvent(parent, child, ev);
}
3、
此处容易误导,没必要再新建对象
UcNewsHeaderPagerBehavior类中
private void start() {
if (mOverScroller.computeScrollOffset()) {
ViewCompat.postOnAnimation(mLayout, this);
} else {
onFlingFinished(mParent, mLayout);
}
}
效果拆分
image.pngHeaderPager向上滑动(NewsTitle伴随着HeaderPager的滑动向下滑动,NewsTabs伴随着HeaderPager的滑动向上滑动,NewsContent伴随着HeaderPager的滑动向上滑动)
需要解决的问题:
1、
初始状态隐藏NewsTitle和NewsTabs(这个其实不是隐藏了,只是看不见了,被NewsContent给遮住了),NewsContent固定在HeaderPager下面
隐藏NewsTitle:设置NewsTitle的margin_Top值为height的负数即可。
隐藏NewsTabs、重新设置NewsContent的位置:
Behavior中
@Override
protected void layoutChild(final CoordinatorLayout parent, final View child, final int layoutDirection) {
final List<View> dependencies = parent.getDependencies(child);
final View header = findFirstDependency(dependencies);
if (header != null) {
final CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
final Rect available = mTempRect1;
available.set(parent.getPaddingLeft() + lp.leftMargin, header.getBottom() + lp.topMargin,
parent.getWidth() - parent.getPaddingRight() - lp.rightMargin,
parent.getHeight() + header.getBottom() - parent.getPaddingBottom() - lp.bottomMargin);
final Rect out = mTempRect2;
GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(), child.getMeasuredHeight(), available, out, layoutDirection);
final int overlap = getOverlapPixelsForOffset(header);
child.layout(out.left, out.top - overlap, out.right, out.bottom - overlap);
mVerticalLayoutGap = out.top - header.getBottom();
} else {
// If we don't have a dependency, let super handle it
super.layoutChild(parent, child, layoutDirection);
mVerticalLayoutGap = 0;
}
}
2、
初始状态下,禁止viewpager消费事件(其实就是禁止它消费水平滑动事件,竖直方向的事件它本身就不会去消费),这样的话事件会传递给起子view(在这里是Recyclerview),在NestedScroll机制中,Recyclerview会先将事件传递给起父容器(CoordinatorLayout)
第一步:重写ViewPager的Behavior的onInterceptTouchEvent事件
UcNewsContentBehavior#onInterceptTouchEvent
@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {
//初始状态下,不让viewpager左右滑动,所以水平滑动要拦截,竖直不拦截
//不用拦截竖直滑动事件,因为viewpager并不会消费竖直滑动事件,事件会传递到Recyclerview
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
lastX = ev.getX();
lastY = ev.getY();
}
if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
float currX = ev.getX();
float currY = ev.getY();
float delta=Math.abs(currX - lastX);
boolean horizontalScroll =delta>touchslop;
if (horizontalScroll && child.getTranslationY() == 0)
{
return true;
}
}
return super.onInterceptTouchEvent(parent, child, ev);
}
事件拦截后,接下来该关注HeaderPager的Behavior了,事件由Recyclerview传递给CoordinatorLayout后,CoordinatorLayout会遍历所有直接值view,存在Behavior的就会调用Behavior中的方法,在这个例子中是由HeaderPager的Behavior来消费事件的:
我们按照回调方法的先后顺序来介绍
UcNewsHeaderPagerBehavior#onStartNestedScroll
表示要消费竖直方向的事件
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "onStartNestedScroll: ");
}
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
UcNewsHeaderPagerBehavior#onNestedPreScroll
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
//dy>0 scroll up;dy<0,scroll down
//we don't consume at the first time when header is not closed and scroll down,maybe NestedScrollingChild need it now,
// we consume the rest dy in onNestedScroll method
Log.e("abc","getTranlationY###"+child.getTranslationY());
if (child.getTranslationY() >= getHeaderOffsetRange() && dy < 0)
return;
//滑动太灵敏了,这里只将滑动距离的四分之一作用到child上
float halfOfDis = dy / 4.0f;
if (!canScroll(child, halfOfDis)) {
child.setTranslationY(halfOfDis > 0 ? getHeaderOffsetRange() : 0);
} else {
child.setTranslationY(child.getTranslationY() - halfOfDis);
//表示事件全部都消费掉了,不给Recyclerview了
consumed[1] = dy;
}
}
UcNewsHeaderPagerBehavior#onNestedScroll
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
//(如果不重写这个方法,滑动到顶部(tab置顶),再往下滑动时无法滑动)
float halfOfDis = dyUnconsumed / 4.0f;
if (!canScroll(child, halfOfDis)) {
child.setTranslationY(halfOfDis > 0 ? getHeaderOffsetRange() : 0);
} else {
child.setTranslationY(child.getTranslationY() - halfOfDis);
}
}
UcNewsHeaderPagerBehavior#onNestedPreFling
@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {
// consumed the flinging behavior until Closed
//当tab置顶的时候不要拦截fling事件了,要不然Recyclerview的fling事件都不起作用了,
//但是没有置顶的时候是需要拦截的,否则tab还没有置顶,Recyclerview就会惯性滑动
return !isClosed(child);
}
UcNewsHeaderPagerBehavior#onStopNestedScroll
/**
* settle here
*/
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
settle(coordinatorLayout, child);
}
private void settle(CoordinatorLayout parent, final View child) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "settle: ");
}
if (mFlingRunnable != null) {
child.removeCallbacks(mFlingRunnable);
mFlingRunnable = null;
}
mFlingRunnable = new FlingRunnable(parent, child);
if (child.getTranslationY() < getHeaderOffsetRange() / 3.0f) {
mFlingRunnable.scrollToClosed(DURATION_SHORT);
} else {
mFlingRunnable.scrollToOpen(DURATION_SHORT);
}
}
private class FlingRunnable implements Runnable {
private final CoordinatorLayout mParent;
private final View mLayout;
FlingRunnable(CoordinatorLayout parent, View layout) {
mParent = parent;
mLayout = layout;
}
public void scrollToClosed(int duration) {
float curTranslationY = ViewCompat.getTranslationY(mLayout);
float dy = getHeaderOffsetRange() - curTranslationY;
if (BuildConfig.DEBUG) {
Log.d(TAG, "scrollToClosed:offest:" + getHeaderOffsetRange());
Log.d(TAG, "scrollToClosed: cur0:" + curTranslationY + ",end0:" + dy);
Log.d(TAG, "scrollToClosed: cur:" + Math.round(curTranslationY) + ",end:" + Math.round(dy));
Log.d(TAG, "scrollToClosed: cur1:" + (int) (curTranslationY) + ",end:" + (int) dy);
}
mOverScroller.startScroll(0, Math.round(curTranslationY - 0.1f), 0, Math.round(dy + 0.1f), duration);
start();
}
public void scrollToOpen(int duration) {
float curTranslationY = ViewCompat.getTranslationY(mLayout);
mOverScroller.startScroll(0, (int) curTranslationY, 0, (int) -curTranslationY, duration);
start();
}
private void start() {
if (mOverScroller.computeScrollOffset()) {
ViewCompat.postOnAnimation(mLayout, this);
} else {
onFlingFinished(mParent, mLayout);
}
}
@Override
public void run() {
if (mLayout != null && mOverScroller != null) {
if (mOverScroller.computeScrollOffset()) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "run: " + mOverScroller.getCurrY());
}
ViewCompat.setTranslationY(mLayout, mOverScroller.getCurrY());
ViewCompat.postOnAnimation(mLayout, this);
} else {
onFlingFinished(mParent, mLayout);
}
}
}
}
HeaderPager的Behavior分析完了,其他view都是依赖HeaderPager这个View的,所以他们的Behavior也相对比较简单
分析NewsContent这个view的Behavior
核心逻辑:
首先:
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return isDependOn(dependency);
}
private boolean isDependOn(View dependency) {
return dependency != null && dependency.getId() == R.id.id_uc_news_header_pager;
}
接着
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "onDependentViewChanged");
}
offsetChildAsNeeded(parent, child, dependency);
return true;
}
private void offsetChildAsNeeded(CoordinatorLayout parent, View child, View dependency) {
child.setTranslationY((int) (-dependency.getTranslationY() / (getHeaderOffsetRange() * 1.0f) * getScrollRange(dependency)));
}