關於 NestedScrollView 和CoordinateLayout的互動 以及CoordinateLayout的分發
上一次說到,一般很少有behavior去重寫behavior.onTouchEvent和behavior.onInterceptTouchEvent方法。那麼其實我們可以直接忽略這一套流程,直接當他是正常的事件分發啦。
那麼現在模擬這麼一個情況,我們手指滑動NestedScrollView的項(設定其xml屬性:app:layout_behavior=”@string/appbar_scrolling_view_behavior”),
然後AppbarLayout隨之滾動。這是怎麼發生的呢?
如果是正常的事件分發流程,我們會到達NestedScrollView的onInterceptTouchEvent 然後是onTouchEvent
onInterceptTouchEvent:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
//mIsBeingDragged 關鍵標誌位。如果已經進入滾動狀態
if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
return true;
}
switch (action & MotionEventCompat.ACTION_MASK) {
case......
..............
這個方法主要是判定是否要攔截垂直滾動的事件給自己消費,如果真的要攔截就返回true。 具體的內部判斷就不貼上了,因為不是今天的主角,大體上看過去,ACTION_MOVE 的話,如果超過一個y軸分量閾值那麼就可以判定進入了滾動狀態。ACTION_DOWN的話,如果當前滾動動畫還沒有結束(比如手指fling操作),那麼現在按下去的話也算是一種拖動了(這裡還不是很確定)
然後如果真的是上下拖動並在onInterceptTouchEvent 裡頭返回了true,事件流就跑到這個類的onTouchEvent方法內了。
onTouchEvent內部也是一個Switch case來分別處理不同型別的事件
自然的先從 ACTION_DOWN 開始
關鍵的地方出現了
....
case MotionEvent.ACTION_DOWN: {
....
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
.....
}
....
好了主角來了startNestedScroll
@Override
public boolean startNestedScroll(int axes) {
return mChildHelper.startNestedScroll(axes);
}
跟進去
public boolean startNestedScroll(int axes) {
if (hasNestedScrollingParent()) {
// Already in progress
return true;
}
if (isNestedScrollingEnabled()) {
ViewParent p = mView.getParent();//得到父view
View child = mView;
while (p != null) {
if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {//回撥父類的onStartNestedScroll方法!
mNestedScrollingParent = p;
ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);//回撥父類的onNestedScrollAccepted方法!
return true;
}
if (p instanceof View) {
child = (View) p;
}
p = p.getParent();//如果回撥失敗,說明不是CoordinateLayout,那麼繼續向上找
}
}
return false;
}
其實吧ViewGroup裡頭也有onStartNestedScroll和onNestedScrollAccepted方法,不過預設返回為false,而CoordinateLayout對他們進行了相應的重寫。所以如果呼叫的父view返回false,那麼
一種可能是這個父view不是CoordinateLayout,那麼就繼續沿著樹向上找
另一個可能是這個父view是CoordinateLayout,但是這個父view的子view的behavior全都沒有接受這個事件,那麼也繼續向上看看有沒有更高層級的CoordinateLayout來接收
那麼接下來就去CoordinateLayout看看實現
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
boolean handled = false;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
nestedScrollAxes);
handled |= accepted;
lp.acceptNestedScroll(accepted);
//在子view的lp裡頭記錄這個子view的behavior是否接受了事件!以後就靠著這個標誌位來分發接下來的流程了!
} else {
lp.acceptNestedScroll(false);
//記錄子view的behaviior並沒有接受這個事件
}
}
return handled;
}
Coor依次呼叫了所有子view的behavior(如果有的話)的相應的onStartNestedScroll方法,並且返回是否至少一個behavior處理了這個事件
。
忽然想到了一個很好的比喻,這裡Coor起了一個類似集線器的作用,各個子view相當於連在上頭的電腦,他們整體構成一個星型拓撲的區域網結構。Coor負責轉發某臺電腦的請求給所有區域網內的連線。至於那臺電腦要如何處理這個請求那就是電腦自己的事情。
繼續看:
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
mNestedScrollingDirectChild = child;
mNestedScrollingTarget = target;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
//如果在lp記錄的是不接受事件,那直接continue跳過就好了
if (!lp.isNestedScrollAccepted()) {
continue;
}
// 如果剛才lp記錄的是接受
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
//進一步呼叫子view的onNestedScrollAccepted
viewBehavior.onNestedScrollAccepted(this, view, child, target, nestedScrollAxes);
}
}
}
好了回到NestedScroll的onTouchEvent,剛才說完了ACTION_DOWN,現在自然是來看ACTION_MOVE 了
case MotionEvent.ACTION_MOVE:{
....
dispatchNestedPreScroll
...
dispatchNestedScroll
...
...
}
後面的套路其實和之前的onNestedScrollAccepted都差不多,同樣也是會回撥父view的Coor的相應的方法,然後Coor在相應方法裡頭遍歷所有子view,首先檢查子view的lp是否接受了這個事件流,如果是,就接著對子view的behavior進行相應的回撥。就不羅嗦太多了
同理我們可以分析Recyclerview,在RecyclerView的onTouchEvent方法裡頭同樣的也是用startNestedScroll 來完成上面的一系列動作,思路類似,也不繼續分析了