view系列源碼分析之CoordinatorLayout,嵌套滑動(dòng),自定義Behavior分析(3)

上文說了CoordinatorLayout其子view的measure和layout后挫掏,要探究其最重要的滑動(dòng)機(jī)制了,我們都知道在appbarlayout的子view設(shè)置了scroll_flag為scroll屬性時(shí)會(huì)出現(xiàn)滑動(dòng)現(xiàn)象那首先提一個(gè)問題:
1.為何觸摸到appBarlayout時(shí),nestScrollview會(huì)跟著appBarLayout就行滑動(dòng)

這兩個(gè)問題如果在linearlayout里當(dāng)然用不著問巩割,但是細(xì)細(xì)一想在coordinatorLayout里nestScrollview在appBarlayout下方本來就是onlayout里設(shè)置的,那滑動(dòng)肯定的話肯定也做了特殊的處理,當(dāng)然牽扯到滑動(dòng)就要進(jìn)行事件分發(fā)了焕窝,不了解的朋友可以看下事件分發(fā)之結(jié)論篇嫉戚。當(dāng)然默認(rèn)先從coordinatorLayout的onInterceptTouchEvent說起:

@Override
   public boolean onInterceptTouchEvent(MotionEvent ev) {
       MotionEvent cancelEvent = null;

       final int action = ev.getActionMasked();

       // Make sure we reset in case we had missed a previous important event.
       if (action == MotionEvent.ACTION_DOWN) {
           resetTouchBehaviors(true);
       }

       final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);

       if (cancelEvent != null) {
           cancelEvent.recycle();
       }

       if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
           resetTouchBehaviors(true);
       }

       return intercepted;
   }

很明顯重點(diǎn)在于performIntercept方法刨裆,讓我們來看一下

private boolean performIntercept(MotionEvent ev, final int type) {
        boolean intercepted = false;
        boolean newBlock = false;

        MotionEvent cancelEvent = null;

        final int action = ev.getActionMasked();

        final List<View> topmostChildList = mTempList1;
        getTopSortedChildren(topmostChildList);

        // Let topmost child views inspect first
        final int childCount = topmostChildList.size();
        for (int i = 0; i < childCount; i++) {
            final View child = topmostChildList.get(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final Behavior b = lp.getBehavior();

            if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {
                // Cancel all behaviors beneath the one that intercepted.
                // If the event is "down" then we don't have anything to cancel yet.
                if (b != null) {
                    if (cancelEvent == null) {
                        final long now = SystemClock.uptimeMillis();
                        cancelEvent = MotionEvent.obtain(now, now,
                                MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
                    }
                    switch (type) {
                        case TYPE_ON_INTERCEPT:
                            b.onInterceptTouchEvent(this, child, cancelEvent);
                            break;
                        case TYPE_ON_TOUCH:
                            b.onTouchEvent(this, child, cancelEvent);
                            break;
                    }
                }
                continue;
            }

            if (!intercepted && b != null) {
                switch (type) {
                    case TYPE_ON_INTERCEPT:
                        intercepted = b.onInterceptTouchEvent(this, child, ev);
                        break;
                    case TYPE_ON_TOUCH:
                        intercepted = b.onTouchEvent(this, child, ev);
                        break;
                }
                if (intercepted) {
                    mBehaviorTouchView = child;
                }
            }

            // Don't keep going if we're not allowing interaction below this.
            // Setting newBlock will make sure we cancel the rest of the behaviors.
            final boolean wasBlocking = lp.didBlockInteraction();
            final boolean isBlocking = lp.isBlockingInteractionBelow(this, child);
            newBlock = isBlocking && !wasBlocking;
            if (isBlocking && !newBlock) {
                // Stop here since we don't have anything more to cancel - we already did
                // when the behavior first started blocking things below this point.
                break;
            }
        }

        topmostChildList.clear();

        return intercepted;
    }

這里獲取到topmostChildList然后進(jìn)行循環(huán),對(duì)mBehaviorTouchView就行了賦值也就是這個(gè)coordinatorLayout的事件交給了哪個(gè)子view的behavior進(jìn)行處理彬檀,先來看下nestScrollview處理了沒有,很明顯在其headScrollbehavior里沒有對(duì)其進(jìn)行復(fù)寫帆啃,也就說取得就是默認(rèn)值也就是false,然后看下appBarlayout的headBehavior

@Override
   public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
       if (mTouchSlop < 0) {
           mTouchSlop = ViewConfiguration.get(parent.getContext()).getScaledTouchSlop();
       }

       final int action = ev.getAction();

       // Shortcut since we're being dragged
       if (action == MotionEvent.ACTION_MOVE && mIsBeingDragged) {
           return true;
       }

       switch (ev.getActionMasked()) {
           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;
           }

           case MotionEvent.ACTION_MOVE: {
               final int activePointerId = mActivePointerId;
               if (activePointerId == INVALID_POINTER) {
                   // If we don't have a valid id, the touch down wasn't on content.
                   break;
               }
               final int pointerIndex = ev.findPointerIndex(activePointerId);
               if (pointerIndex == -1) {
                   break;
               }

               final int y = (int) ev.getY(pointerIndex);
               final int yDiff = Math.abs(y - mLastMotionY);
               if (yDiff > mTouchSlop) {
                   mIsBeingDragged = true;
                   mLastMotionY = y;
               }
               break;
           }

           case MotionEvent.ACTION_CANCEL:
           case MotionEvent.ACTION_UP: {
               mIsBeingDragged = false;
               mActivePointerId = INVALID_POINTER;
               if (mVelocityTracker != null) {
                   mVelocityTracker.recycle();
                   mVelocityTracker = null;
               }
               break;
           }
       }

       if (mVelocityTracker != null) {
           mVelocityTracker.addMovement(ev);
       }

       return mIsBeingDragged;
   }

這里我們看到在down的時(shí)候coordinatorLayout和appBarlayout都是返回的false,也就是說是交給appBarlayout的ontouchEvent處理的窍帝,然后在move的時(shí)候努潘,如果當(dāng)手指落在appBarlayout范圍內(nèi)且移動(dòng)了,appBarlayout的攔截事件返回了true坤学,那也就是說此時(shí)coordinatorLayout的攔截事件也返回了true疯坤,并會(huì)給appBarlayout的onTouchEvent事件發(fā)送一個(gè)cancel事件,就表明此事件要自己處理了深浮,也就是說當(dāng)在move的時(shí)候會(huì)調(diào)用coordinatorLayout的ontouchevent方法压怠,我們來看下

@Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean handled = false;
        boolean cancelSuper = false;
        MotionEvent cancelEvent = null;

        final int action = ev.getActionMasked();

        if (mBehaviorTouchView != null || (cancelSuper = performIntercept(ev, TYPE_ON_TOUCH))) {
            // Safe since performIntercept guarantees that
            // mBehaviorTouchView != null if it returns true
            final LayoutParams lp = (LayoutParams) mBehaviorTouchView.getLayoutParams();
            final Behavior b = lp.getBehavior();
            if (b != null) {
                handled = b.onTouchEvent(this, mBehaviorTouchView, ev);
            }
        }

        // Keep the super implementation correct
        if (mBehaviorTouchView == null) {
            handled |= super.onTouchEvent(ev);
        } else if (cancelSuper) {
            if (cancelEvent == null) {
                final long now = SystemClock.uptimeMillis();
                cancelEvent = MotionEvent.obtain(now, now,
                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
            }
            super.onTouchEvent(cancelEvent);
        }

        if (!handled && action == MotionEvent.ACTION_DOWN) {

        }

        if (cancelEvent != null) {
            cancelEvent.recycle();
        }

        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            resetTouchBehaviors(false);
        }

        return handled;
    }

這里mBehaviorTouchView肯定是有的,也就是appBarlayout,進(jìn)入這個(gè)if語句飞苇,發(fā)現(xiàn)其還是交給appBarlayout的onTouchEvent處理的

@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;
    }

到這里相信大家能叫看出來了菌瘫,move的時(shí)候簡單來說就調(diào)用了scroll方法最終也是調(diào)用了其

@Override
       int setHeaderTopBottomOffset(CoordinatorLayout coordinatorLayout,
                                    AppBarLayout appBarLayout, int newOffset, int minOffset, int maxOffset) {
           final int curOffset = getTopBottomOffsetForScrollingSibling();
        
           int consumed = 0;

           if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) {
               // If we have some scrolling range, and we're currently within the min and max
               // offsets, calculate a new offset
               newOffset = MathUtils.clamp(newOffset, minOffset, maxOffset);
               if (curOffset != newOffset) {
                   final int interpolatedOffset = appBarLayout.hasChildWithInterpolator()
                           ? interpolateOffset(appBarLayout, newOffset)
                           : newOffset;

                   final boolean offsetChanged = setTopAndBottomOffset(interpolatedOffset);

                   // Update how much dy we have consumed
                   consumed = curOffset - newOffset;
               
                   // Update the stored sibling offset
                   mOffsetDelta = newOffset - interpolatedOffset;

                   if (!offsetChanged && appBarLayout.hasChildWithInterpolator()) {
                       // If the offset hasn't changed and we're using an interpolated scroll
                       // then we need to keep any dependent views updated. CoL will do this for
                       // us when we move, but we need to do it manually when we don't (as an
                       // interpolated scroll may finish early).
                       coordinatorLayout.dispatchDependentViewsChanged(appBarLayout);
                   }

                   // Dispatch the updates to any listeners
                   appBarLayout.dispatchOffsetUpdates(getTopAndBottomOffset());

                   // Update the AppBarLayout's drawable state (for any elevation changes)
                   updateAppBarLayoutDrawableState(coordinatorLayout, appBarLayout, newOffset,
                           newOffset < curOffset ? -1 : 1, false);
               }
           } else {
               // Reset the offset delta
               mOffsetDelta = 0;
           }

           return consumed;
       }

這個(gè)consumed也就是相對(duì)于每次滑動(dòng)多少距離,當(dāng)然向下滑動(dòng)的話
consumed是>0的,向上反之布卡。
setTopAndBottomOffset方法最終也是調(diào)用了ViewOffsetHelper的updateOffsets

 private void updateOffsets() {

       ViewCompat.offsetTopAndBottom(mView, mOffsetTop - (mView.getTop() - mLayoutTop));

       ViewCompat.offsetLeftAndRight(mView, mOffsetLeft - (mView.getLeft() - mLayoutLeft));
   }

可以看到并不是通過scroller滑動(dòng)的雨让,直接的改變了位置,而mLayoutTop就是我們一開始傳進(jìn)來初始的appBarlayout的位置

那我們分析到現(xiàn)在羽利,也沒有找到問題一的答案宫患,為何appBarlayout滑動(dòng)了,nestScrollview會(huì)滑動(dòng)?

其實(shí)啊當(dāng)調(diào)用ViewCompat.offsetTopAndBottom方法后娃闲,大家可以看下其源碼會(huì)進(jìn)行一次重繪虚汛,這并不會(huì)調(diào)用次view的draw方法,因?yàn)樵创a里其設(shè)置的invalidateCache參數(shù)是false皇帮,也就是說少了一個(gè)標(biāo)志卷哩,
但是對(duì)整個(gè)view樹會(huì)執(zhí)行performTraversals方法,那有啥監(jiān)聽可以監(jiān)聽到么,其實(shí)是有的也就是preDrawListener

boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;

      if (!cancelDraw && !newSurface) {
          if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
              for (int i = 0; i < mPendingTransitions.size(); ++i) {
                  mPendingTransitions.get(i).startChangingAnimations();
              }
              mPendingTransitions.clear();
          }

          performDraw();
      } else {
}

也就是在執(zhí)行performDraw的上面會(huì)執(zhí)行dispatchOnPreDraw也就是會(huì)調(diào)用onPreDraw的監(jiān)聽属拾,我們?cè)賮砜聪耤oordinatorLayout的onpreDraw

 class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
        @Override
        public boolean onPreDraw() {
            onChildViewsChanged(EVENT_PRE_DRAW);
            return true;
        }
    }

很明顯又回到了這里将谊,也就是第一篇文章分析的那里,其實(shí)又回調(diào)了onDependentViewChanged方法渐白,這才導(dǎo)致了nestScrollview的滑動(dòng)
也就真相大白了尊浓,想不到一個(gè)簡單的滑動(dòng)在coordinatorLayout竟如此繞,這也是它可以定制化的魅力所在吧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末纯衍,一起剝皮案震驚了整個(gè)濱河市栋齿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌襟诸,老刑警劉巖瓦堵,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異歌亲,居然都是意外死亡菇用,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門陷揪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惋鸥,“玉大人,你說我怎么就攤上這事鹅龄】剑” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵扮休,是天一觀的道長。 經(jīng)常有香客問我拴鸵,道長玷坠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任劲藐,我火速辦了婚禮八堡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘聘芜。我一直安慰自己兄渺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布汰现。 她就那樣靜靜地躺著挂谍,像睡著了一般叔壤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上口叙,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天炼绘,我揣著相機(jī)與錄音,去河邊找鬼妄田。 笑死俺亮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的疟呐。 我是一名探鬼主播脚曾,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼启具!你這毒婦竟也來了斟珊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤富纸,失蹤者是張志新(化名)和其女友劉穎囤踩,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晓褪,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡堵漱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了涣仿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勤庐。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖好港,靈堂內(nèi)的尸體忽然破棺而出愉镰,到底是詐尸還是另有隱情,我是刑警寧澤钧汹,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布丈探,位于F島的核電站,受9級(jí)特大地震影響拔莱,放射性物質(zhì)發(fā)生泄漏碗降。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一塘秦、第九天 我趴在偏房一處隱蔽的房頂上張望讼渊。 院中可真熱鬧,春花似錦尊剔、人聲如沸爪幻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挨稿。三九已至仇轻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間叶组,已是汗流浹背拯田。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留甩十,地道東北人船庇。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像侣监,于是被迫代替她去往敵國和親鸭轮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容