协调者布局头部无法上滑的浅析

场景

某个界面的头部HeaderBehavior是动态生成的,高度不定,某次在头部超过整屏的时候,上滑显示列表,再下拉,当我再次想要上滑的时候,发现无法滑动了

原因

  • 协调者布局会根据手指触摸的落点是否在头部布局的范围内来判断由HeaderBehavior来处理滑动还是RecycleView或者NestedScrollerView来处理滑动
 case MotionEvent.ACTION_DOWN: {
                mIsBeingDragged = false;
                final int x = (int) ev.getX();
                final int y = (int) ev.getY();
                if (canDragView(child) && parent.isPointInChildBounds(child, x, y)) {
                    mLastMotionY = y;
                    mActivePointerId = ev.getPointerId(0);
                    ensureVelocityTracker();
                }
                break;
            }
  public boolean isPointInChildBounds(View child, int x, int y) {
        final Rect r = acquireTempRect();
        getDescendantRect(child, r);
        try {
            return r.contains(x, y);
        } finally {
            releaseTempRect(r);
        }
    }
  • AppBarLayout对滑动的子View有弱引用(也就是RecycleView或者NestedScrollerView)
  • 当这个弱引用存在的时候判断子View是否能继续滑动,如果能继续滑动,则交由子View处理(也就是这里的问题导致我Head过大时,落点在Head,导致无法滑动),如果子View不能滑动,在onTouchEvent事件中把MotionEvent.ACTION_DOWN交给HeaderBehavior自己来处理
   @Override
        boolean canDragView(AppBarLayout view) {
            if (mOnDragCallback != null) {
                // If there is a drag callback set, it's in control
                return mOnDragCallback.canDrag(view);
            }

            // Else we'll use the default behaviour of seeing if it can scroll down
            if (mLastNestedScrollingChildRef != null) {
                // If we have a reference to a scrolling view, check it
                final View scrollingView = mLastNestedScrollingChildRef.get();
                return scrollingView != null && scrollingView.isShown()
                        && !ViewCompat.canScrollVertically(scrollingView, -1);
            } else {
                // Otherwise we assume that the scrolling view hasn't been scrolled and can drag.
                return true;
            }
        }

解决方案

那么从上述分析中我们可以得出两点,如果要滑动,以我鄙见,有两个方案,想办法让Header来处理,或者同时让RecycleView来接管不属于他的事件

  • 先看怎么让Header来处理

    从上述部分代码我们可以看到canDragView在有弱引用的时候返回为true的条件有scrollingView(此时为recycleview)不为空,且是显示状态,且canScrollVertically返回为false

    • 那么问题来了,recycleview的canScrollVertically这个方法什么时候返回为false

         /**
           * Query if vertical scrolling is currently supported. The default implementation
           * returns false.
           *
           * @return True if this LayoutManager can scroll the current contents vertically
           */
          public boolean canScrollVertically() {
              return false;
          }
    • 默认返回false,但是当有LayoutManager为竖直方向是返回为true。OK,那么我们只要在自己的recycleview中重新canScrollVertically方法就好了(经我测试,默认返回为false可以解决无法滑动的问题,但是为了更严谨一点,尽量还是做一层判断,某些第三方可能将下拉刷新的逻辑引入了canScrollVertically的判断,默认返回为false可能导致无法下拉刷新)

  • 再看看怎么让RecycleView来处理

    • 简单,在 AppBarLayout的touch监听中直接把事件交给recycleview
    appbarlayout.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                recycleview.dispatchTouchEvent(event);
                return false;
            }
        });
    • 经我测试,有效,并且不会对header中本身的控件点击事件行为产生影响

结语

虽然解决了问题,也提供了两个方案,但是从代码来看,我的解决方案都是很粗暴的,短时间来看没有什么毛病,但是真的没有毛病吗?这是要打一个问号的

你可能感兴趣的:(协调者布局头部无法上滑的浅析)