问题现象: CoordinatorLayout嵌套 上半部分AppBarLayout+下半部分RecyclerView, 对下半部分RecyclerView的显示隐藏做操作(如吟唱recyclerView展示一个ErrorLayout or EmptyLayout, 会导致AppBarLayout区域不再响应滑动, 这个bug较容易忽略, 因为滑动界面上的下半部分立即可以恢复滑动使现象消失.
这个问题是由系统源码导致, 里记录一下原因分析:
从CoordinatorLayout看起:
HeaderBehavior:
AppBarlayout:
lastNestedScrollingChildRef的赋值只有两处,
less
@Override
public boolean onStartNestedScroll(
@NonNull CoordinatorLayout parent,
@NonNull T child,
@NonNull View directTargetChild,
View target,
int nestedScrollAxes,
int type) {
//省略...
// A new nested scroll has started so clear out the previous ref
lastNestedScrollingChildRef = null;
//省略...
return started;
}
@Override
public void onStopNestedScroll(
CoordinatorLayout coordinatorLayout, @NonNull T abl, View target, int type) {
//省略...
// Keep a reference to the previous nested scrolling child
lastNestedScrollingChildRef = new WeakReference<>(target);
}
所以lastNestedScrollingChildRef得含义和名字一样, 是上一次的嵌套滚动view, 如果我们滑动了RecyclerView, 那么这个值就是RecyclerView, 如果此时把RecyclerView隐藏, 再滑动AppBarLayout区域, 就会滑不动了, 因为AppBarLayout中CanDragView中判断RecyclerView isShown()
为false.
为什么滑动一下AppBarLayout下面的错误界面或空界面区域就会恢复呢? 因为在View中有如下一段代码:
scss
public boolean dispatchTouchEvent(MotionEvent event) {
//...
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
//...
由前面源码得知, stopNestedScroll();
中会对lastNestedScrollingChildRef重新赋值. 这也是为什么是在 lastNestedScrollingChildRef是在stopNestedScroll();
中赋值, 而每次开始滑动是lastNestedScrollingChildRef总是不为空.
至此, AppBarLayout偶现滑不动的原因就清楚了.