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

上文說了一個(gè)behavior的創(chuàng)建和也知道了依賴的產(chǎn)生原理异赫。這篇文章來說說兩點(diǎn):
1appBarlayout為何在狀態(tài)欄下面:
2..NestScrollView為何在appBarlayout的下面:

讓我們繼續(xù)看CoordinatorLayout的onMeasure方法

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        prepareChildren();
        ensurePreDrawListener();

        final int paddingLeft = getPaddingLeft();
        final int paddingTop = getPaddingTop();
        final int paddingRight = getPaddingRight();
        final int paddingBottom = getPaddingBottom();
        final int layoutDirection = ViewCompat.getLayoutDirection(this);
        final boolean isRtl = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        final int widthPadding = paddingLeft + paddingRight;
        final int heightPadding = paddingTop + paddingBottom;
        int widthUsed = getSuggestedMinimumWidth();
        int heightUsed = getSuggestedMinimumHeight();
        int childState = 0;

        final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this);

        final int childCount = mDependencySortedChildren.size();
        for (int i = 0; i < childCount; i++) {
            final View child = mDependencySortedChildren.get(i);
            if (child.getVisibility() == GONE) {
                // If the child is GONE, skip...
                continue;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            int keylineWidthUsed = 0;
            
            }

            int childWidthMeasureSpec = widthMeasureSpec;
            int childHeightMeasureSpec = heightMeasureSpec;
            if (applyInsets && !ViewCompat.getFitsSystemWindows(child)) {
                // We're set to handle insets but this child isn't, so we will measure the
                // child as if there are no insets
                final int horizInsets = mLastInsets.getSystemWindowInsetLeft()
                        + mLastInsets.getSystemWindowInsetRight();
                final int vertInsets = mLastInsets.getSystemWindowInsetTop()
                        + mLastInsets.getSystemWindowInsetBottom();

                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                        widthSize - horizInsets, widthMode);
                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                        heightSize - vertInsets, heightMode);
            }

            final Behavior b = lp.getBehavior();
            if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
                    childHeightMeasureSpec, 0)) {
                onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
                        childHeightMeasureSpec, 0);
            }

            widthUsed = Math.max(widthUsed, widthPadding + child.getMeasuredWidth() +
                    lp.leftMargin + lp.rightMargin);

            heightUsed = Math.max(heightUsed, heightPadding + child.getMeasuredHeight() +
                    lp.topMargin + lp.bottomMargin);
            childState = View.combineMeasuredStates(childState, child.getMeasuredState());
        }

        final int width = View.resolveSizeAndState(widthUsed, widthMeasureSpec,
                childState & View.MEASURED_STATE_MASK);
        final int height = View.resolveSizeAndState(heightUsed, heightMeasureSpec,
                childState << View.MEASURED_HEIGHT_STATE_SHIFT);
        setMeasuredDimension(width, height);
    }

我們可以很清楚的看到首先是獲取了父view的Measurespec的寬高,當(dāng)然這里會(huì)默認(rèn)進(jìn)行2次measure打印在模擬器上分別是1488和1704,很多人會(huì)覺得奇怪,其實(shí)任何自定義的view都會(huì)進(jìn)行2次measure,因?yàn)闀?huì)2次調(diào)用viewRootImp的測(cè)量方法,第一次是父view給的寬高是獲取屏幕密度產(chǎn)生的高度,第二次才是我們通常所說的高度,由于在1920的手機(jī)上不包含狀態(tài)欄和虛擬按鍵,所以1704就是coordinatorLayout的高度了挟秤,在代碼上,如果applyInsets不為空的話抄伍,高度就是1776也就說會(huì)加上狀態(tài)欄艘刚,那appBarlayout的話是不包含的,所以會(huì)減去狀態(tài)欄高度截珍,這在源碼里也有體現(xiàn)昔脯。
在測(cè)量子view的時(shí)候啄糙,這里用到了Behavior,分別測(cè)量了appBarlayout和NestScrollview,首先看appBarlayout

 @Override
        public boolean onMeasureChild(CoordinatorLayout parent, AppBarLayout child,
                                      int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec,
                                      int heightUsed) {
            final CoordinatorLayout.LayoutParams lp =
                    (CoordinatorLayout.LayoutParams) child.getLayoutParams();
            if (lp.height == CoordinatorLayout.LayoutParams.WRAP_CONTENT) {
                // If the view is set to wrap on it's height, CoordinatorLayout by default will
                // cap the view at the CoL's height. Since the AppBarLayout can scroll, this isn't
                // what we actually want, so we measure it ourselves with an unspecified spec to
                // allow the child to be larger than it's parent
                parent.onMeasureChild(child, parentWidthMeasureSpec, widthUsed,
                        MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightUsed);
                return true;
            }

            // Let the parent handle it as normal
            return super.onMeasureChild(parent, child, parentWidthMeasureSpec, widthUsed,
                    parentHeightMeasureSpec, heightUsed);
        }

當(dāng)給他的高度是wrap_content時(shí),可以看到他允許子view比他更大云稚,也就是說是允許子view滑動(dòng)的隧饼,不是的話,也沒做特殊的處理静陈,直接調(diào)用其父類的了燕雁,再來看下nestScrollview在其HeaderScrollingViewBehavior里重寫的

@Override
   public boolean onMeasureChild(CoordinatorLayout parent, View child,
           int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec,
           int heightUsed) {
       final int childLpHeight = child.getLayoutParams().height;
       if (childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT
               || childLpHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
           // If the menu's height is set to match_parent/wrap_content then measure it
           // with the maximum visible height

           final List<View> dependencies = parent.getDependencies(child);
           final View header = findFirstDependency(dependencies);
           if (header != null) {
               if (ViewCompat.getFitsSystemWindows(header)
                       && !ViewCompat.getFitsSystemWindows(child)) {
                   // If the header is fitting system windows then we need to also,
                   // otherwise we'll get CoL's compatible measuring
                   ViewCompat.setFitsSystemWindows(child, true);

                   if (ViewCompat.getFitsSystemWindows(child)) {
                       // If the set succeeded, trigger a new layout and return true
                       child.requestLayout();
                       return true;
                   }
               }

               int availableHeight = View.MeasureSpec.getSize(parentHeightMeasureSpec);
               if (availableHeight == 0) {
                   // If the measure spec doesn't specify a size, use the current height
                   availableHeight = parent.getHeight();
               }

               final int height = availableHeight - header.getMeasuredHeight()
                       + getScrollRange(header);
               final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height,
                       childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT
                               ? View.MeasureSpec.EXACTLY
                               : View.MeasureSpec.AT_MOST);

               // Now measure the scrolling view with the correct height
               parent.onMeasureChild(child, parentWidthMeasureSpec,
                       widthUsed, heightMeasureSpec, heightUsed);

               return true;
           }
       }
       return false;
   }

重點(diǎn)在

 final int height = availableHeight - header.getMeasuredHeight()
                      + getScrollRange(header);

可以看到他的高度是coordinatorLayout的高度-appBarlayout的高度
+appBarlayou可滑動(dòng)的高度,當(dāng)然我們從appBarlayout里可以看到

public final int getTotalScrollRange() {
        if (mTotalScrollRange != INVALID_SCROLL_RANGE) {
            return mTotalScrollRange;
        }

        int range = 0;
        for (int i = 0, z = getChildCount(); i < z; i++) {
            final View child = getChildAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final int childHeight = child.getMeasuredHeight();
            final int flags = lp.mScrollFlags;

            if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
                // We're set to scroll so add the child's height
                range += childHeight + lp.topMargin + lp.bottomMargin;

                if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
                    // For a collapsing scroll, we to take the collapsed height into account.
                    // We also break straight away since later views can't scroll beneath
                    // us
                    range -= ViewCompat.getMinimumHeight(child);
                    break;
                }
            } else {
                // As soon as a view doesn't have the scroll flag, we end the range calculation.
                // This is because views below can not scroll under a fixed view.
                break;
            }
        }
        return mTotalScrollRange = Math.max(0, range - getTopInset());
    }

他的高度和我們的scroll_flag屬性以及狀態(tài)欄有關(guān)

總的來說這個(gè)可滑動(dòng)高度是<=appBarlayout的高度的

看完了onMeasure再來看下onLayout方法

@Override
   protected void onLayout(boolean changed, int l, int t, int r, int b) {

       final int layoutDirection = ViewCompat.getLayoutDirection(this);
       final int childCount = mDependencySortedChildren.size();
       for (int i = 0; i < childCount; i++) {
           final View child = mDependencySortedChildren.get(i);
           if (child.getVisibility() == GONE) {
               // If the child is GONE, skip...
               continue;
           }

           final LayoutParams lp = (LayoutParams) child.getLayoutParams();
           final Behavior behavior = lp.getBehavior();

           if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
               onLayoutChild(child, layoutDirection);
           }
       }
   }

也很簡(jiǎn)單是同樣的操作
這里appBarlayout的layoutchild首先會(huì)調(diào)用其父類的onlayoutChild

  private void layoutChild(View child, int layoutDirection) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        final Rect parent = acquireTempRect();
        parent.set(getPaddingLeft() + lp.leftMargin,
                getPaddingTop() + lp.topMargin,
                getWidth() - getPaddingRight() - lp.rightMargin,
                getHeight() - getPaddingBottom() - lp.bottomMargin);

        if (mLastInsets != null && ViewCompat.getFitsSystemWindows(this)
                && !ViewCompat.getFitsSystemWindows(child)) {
            // If we're set to handle insets but this child isn't, then it has been measured as
            // if there are no insets. We need to lay it out to match.
            parent.left += mLastInsets.getSystemWindowInsetLeft();
            parent.top += mLastInsets.getSystemWindowInsetTop();
            parent.right -= mLastInsets.getSystemWindowInsetRight();
            parent.bottom -= mLastInsets.getSystemWindowInsetBottom();
        }

        final Rect out = acquireTempRect();
        GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(),
                child.getMeasuredHeight(), parent, out, layoutDirection);
        child.layout(out.left, out.top, out.right, out.bottom);

        releaseTempRect(parent);
        releaseTempRect(out);
    }

可以看到如果父view寫了fitsysystemwindow為true而appBarlayout沒寫鲸拥,會(huì)把他的位置手動(dòng)移到其下面,

這里分析了為何appBarlayout會(huì)在狀態(tài)欄的下方

再說一下nestScollview

 @Override
    protected void layoutChild(final CoordinatorLayout parent, final View child,
            final int layoutDirection) {
        final List<View> dependencies = parent.getDependencies(child);
        final View header = findFirstDependency(dependencies);

        if (header != null) {
            final CoordinatorLayout.LayoutParams lp =
                    (CoordinatorLayout.LayoutParams) child.getLayoutParams();
            final Rect available = mTempRect1;
            available.set(parent.getPaddingLeft() + lp.leftMargin,
                    header.getBottom() + lp.topMargin,
                    parent.getWidth() - parent.getPaddingRight() - lp.rightMargin,
                    parent.getHeight() + header.getBottom()
                            - parent.getPaddingBottom() - lp.bottomMargin);

            final WindowInsetsCompat parentInsets = parent.getLastWindowInsets();
            if (parentInsets != null && ViewCompat.getFitsSystemWindows(parent)
                    && !ViewCompat.getFitsSystemWindows(child)) {
                // If we're set to handle insets but this child isn't, then it has been measured as
                // if there are no insets. We need to lay it out to match horizontally.
                // Top and bottom and already handled in the logic above
                available.left += parentInsets.getSystemWindowInsetLeft();
                available.right -= parentInsets.getSystemWindowInsetRight();
            }

            final Rect out = mTempRect2;
            GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(),
                    child.getMeasuredHeight(), available, out, layoutDirection);

            final int overlap = getOverlapPixelsForOffset(header);

            child.layout(out.left, out.top - overlap, out.right, out.bottom - overlap);
            mVerticalLayoutGap = out.top - header.getBottom();
        } else {
            // If we don't have a dependency, let super handle it
            super.layoutChild(parent, child, layoutDirection);
            mVerticalLayoutGap = 0;
        }
    }

可以看到available的位置是header.getBottom() + lp.topMargin也就是其top在appBarlayout的下面了(一開始就屈居人下),那有啥屬性可以改變呢拐格?可以看到getOverlapPixelsForOffset(head)也就是nestScrollview的Behivor的一個(gè)屬性O(shè)verlay可以改變位置(當(dāng)然滑動(dòng)的交互也會(huì)改了)

這里分析了為何NestScrollview會(huì)在appBarlayout的下方

總結(jié):
基本的布局位置都分析清楚了,主要是對(duì)onMeasure和onlayout進(jìn)行behavior的處理,下篇將討論滑動(dòng)的原理.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末刑赶,一起剝皮案震驚了整個(gè)濱河市捏浊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌撞叨,老刑警劉巖金踪,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異牵敷,居然都是意外死亡胡岔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門枷餐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來靶瘸,“玉大人,你說我怎么就攤上這事毛肋≡惯洌” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵润匙,是天一觀的道長(zhǎng)诗眨。 經(jīng)常有香客問我,道長(zhǎng)趁桃,這世上最難降的妖魔是什么辽话? 我笑而不...
    開封第一講書人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任肄鸽,我火速辦了婚禮卫病,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘典徘。我一直安慰自己蟀苛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開白布逮诲。 她就那樣靜靜地躺著帜平,像睡著了一般幽告。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上裆甩,一...
    開封第一講書人閱讀 52,328評(píng)論 1 310
  • 那天冗锁,我揣著相機(jī)與錄音,去河邊找鬼嗤栓。 笑死冻河,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的茉帅。 我是一名探鬼主播叨叙,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼堪澎!你這毒婦竟也來了擂错?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤樱蛤,失蹤者是張志新(化名)和其女友劉穎钮呀,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刹悴,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡行楞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了土匀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片子房。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖就轧,靈堂內(nèi)的尸體忽然破棺而出证杭,到底是詐尸還是另有隱情,我是刑警寧澤妒御,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布解愤,位于F島的核電站,受9級(jí)特大地震影響乎莉,放射性物質(zhì)發(fā)生泄漏送讲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一惋啃、第九天 我趴在偏房一處隱蔽的房頂上張望哼鬓。 院中可真熱鬧,春花似錦边灭、人聲如沸异希。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽称簿。三九已至扣癣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間憨降,已是汗流浹背父虑。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留授药,地道東北人频轿。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像烁焙,于是被迫代替她去往敵國(guó)和親航邢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359

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