前言
项目示例图
出现的问题链接:https://ask.csdn.net/questions/363070
跟这个哥们遇到的问题一样一样的;
原因
用到的布局结构就是CoordinatorLayout+AppBarLayout+ViewPager 然后Viewpager里是两个RecyclerView;
当你appbar高度低的时候一般不会触发这个问题,因为appbar fling 豪无用武之地。
只有你的appbar的高度到达一定的程度,那么问题就出来。
So You know !! 原因是什么了吧。其实就是appbar 有向下滑动的事件 ,然后你recylerView有向上滑动的事件,两个事件冲突,你往下挪动一下,我往上挪动一下,沙卡拉卡一会。
解决
问题出来首先要找根源,recyclerview 跟appbar 关联的桥梁是什么, Behavior ,具体说是 AppBarLayout.Behavior
但是这个类中并么有处理滑动的东东,so 向上刨根问题。 看他的父类HeaderBehavior
@Override
public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
if (mTouchSlop < 0) {
mTouchSlop = ViewConfiguration.get(parent.getContext()).getScaledTouchSlop();
}
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
final int x = (int) ev.getX();
final int y = (int) ev.getY();
if (parent.isPointInChildBounds(child, x, y) && canDragView(child)) {
mLastMotionY = y;
mActivePointerId = ev.getPointerId(0);
ensureVelocityTracker();
} else {
return false;
}
break;
}
case MotionEvent.ACTION_MOVE: {
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
if (activePointerIndex == -1) {
return false;
}
final int y = (int) ev.getY(activePointerIndex);
int dy = mLastMotionY - y;
if (!mIsBeingDragged && Math.abs(dy) > mTouchSlop) {
mIsBeingDragged = true;
if (dy > 0) {
dy -= mTouchSlop;
} else {
dy += mTouchSlop;
}
}
if (mIsBeingDragged) {
mLastMotionY = y;
// We're being dragged so scroll the ABL
scroll(parent, child, dy, getMaxDragOffset(child), 0);
}
break;
}
case MotionEvent.ACTION_UP:
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(ev);
mVelocityTracker.computeCurrentVelocity(1000);
float yvel = mVelocityTracker.getYVelocity(mActivePointerId);
fling(parent, child, -getScrollRangeForDragFling(child), 0, yvel);
}
// $FALLTHROUGH
case MotionEvent.ACTION_CANCEL: {
mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
}
}
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(ev);
}
return true;
}
看到了吧 在这个类中赋予了滑动属性。 fling(parent, child, -getScrollRangeForDragFling(child), 0, yvel); 这个就是问题出现的原因, fling 还在进行 你就开始向上滑动,肯定有问题啊。
解决办法跟NestedScrolling机制 有关。
不懂这套机制的可以参考这篇文章:https://blog.csdn.net/lmj623565791/article/details/52204039
简单说呢 就是recyclerView滑动前会触发onNestedPreScroll 方法 告诉他爸爸 让他爸爸知道他要开始滑动了,so我们在这个里做操作直接结束Appbar的滑动就行。
代码
import android.content.Context;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.OverScroller;
import java.lang.reflect.Field;
/**
* Created by 于德海 on 2018/4/27.
* package inter.baisong.widgets
* email : yudehai0204@163.com
*
* @describe 自定义behavior 以解决滑动抖动
*
*/
public class CustomBehavior extends AppBarLayout.Behavior {
private OverScroller mScroller;
public CustomBehavior() {
}
public CustomBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
getParentScroller(context);
}
/**
* 反射获得滑动属性。
*
* @param context
*/
private void getParentScroller(Context context) {
if (mScroller != null) return;
mScroller = new OverScroller(context);
try {
Class<?> reflex_class = getClass().getSuperclass().getSuperclass();//父类AppBarLayout.Behavior 父类的父类 HeaderBehavior
Field fieldScroller = reflex_class.getDeclaredField("mScroller");
fieldScroller.setAccessible(true);
fieldScroller.set(this, mScroller);
} catch (Exception e) {}
}
//fling上滑appbar然后迅速fling下滑recycler时, HeaderBehavior的mScroller并未停止, 会导致上下来回晃动
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed, int type) {
if(mScroller!=null){ //当recyclerView 做好滑动准备的时候 直接干掉Appbar的滑动
if (mScroller.computeScrollOffset()) {
mScroller.abortAnimation();
}
}
if (type == ViewCompat.TYPE_NON_TOUCH&&getTopAndBottomOffset() == 0) { //recyclerview 鸡儿的 惯性比较大 会顶在头部一会儿 到头直接干掉它的滑动
ViewCompat.stopNestedScroll(target, type);
}
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
}
@Override
public boolean onTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent e) {
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
break;
}
return super.onTouchEvent(parent,child,e);
}
}
使用位置是在布局文件的appbarlayout中加behavior
示例:
<android.support.design.widget.AppBarLayout
android:id="@+id/mAppbar"
app:elevation="0dip"
app:layout_behavior="inter.****.widgets.CustomBehavior"
android:layout_width="match_parent"
android:layout_height="wrap_content">
结尾
大工告成, game over !