帶著問(wèn)題去看源碼——TextView篇

序言:為什么會(huì)分析這個(gè)問(wèn)題呢,因?yàn)樯洗吾斸旊娫捗嬖囍斜幻嬖嚬賳?wèn)到了,很尷尬的沒(méi)回答出來(lái),View的繪制流程看過(guò)一點(diǎn)源碼,但是感覺(jué)還不夠,像這種View的問(wèn)題能夠延伸出很多問(wèn)題,下面正文開(kāi)始:

Q1:在一個(gè)RelativeLayout中有一個(gè)TextView和一個(gè)Button,當(dāng)點(diǎn)擊Button的時(shí)候給TextView設(shè)置文本,這時(shí)RelativeLayout會(huì)重新測(cè)量嗎?如果會(huì),為什么?

首先我們先大致的想一下這個(gè)問(wèn)題問(wèn)的是關(guān)于哪一塊的知識(shí),如果毫不猶豫上去就是一通回答,這樣顯得太不明智了,我也知道會(huì)重新測(cè)量,為什么?下面我們從源碼的角度去看。既然是設(shè)置文本,那么我們就從TextView的setText中去看看吧:

TextView:


private void setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen) {

    ...
    
    if (mLayout != null) {
        checkForRelayout();
    }
    
    ...

}

在setText中,我找到了這個(gè)checkForRelayout方法,由于我們初始化過(guò)了,所以setText肯定會(huì)執(zhí)行該方法:

TextView:

   /**
     * Check whether entirely new text requires a new view layout
     * or merely a new text layout.
     * 檢查新文本是否需要一個(gè)新的視圖布局
     */
    private void checkForRelayout() {
        // If we have a fixed width, we can just swap in a new text layout
        // if the text height stays the same or if the view height is fixed.
        //如果textview的寬度和高度固定不變的話
        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
                || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
                && (mHint == null || mHintLayout != null)
                && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
            // Static width, so try making a new text layout.

            int oldht = mLayout.getHeight();
            int want = mLayout.getWidth();
            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();

            /*
             * No need to bring the text into view, since the size is not
             * changing (unless we do the requestLayout(), in which case it
             * will happen at measure).
             * 因?yàn)榇笮〔粫?huì)變,所以不需要將文本放入視圖中
             */
            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
                          false);

            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
                // In a fixed-height view, so use our new text layout.
                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
                        && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
                    autoSizeText();
                    invalidate();
                    return;
                }

                // Dynamic height, but height has stayed the same,
                // so use our new text layout.
                //動(dòng)態(tài)高度,但是高度不變
                if (mLayout.getHeight() == oldht
                        && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
                    autoSizeText();
                    invalidate();
                    return;
                }
            }

            // We lose: the height has changed and we have a dynamic height.
            // Request a new view layout using our new text layout.
            requestLayout();
            invalidate();
        } else {
            // Dynamic width, so we have no choice but to request a new
            // view layout with a new text layout.
            //動(dòng)態(tài)寬度,我們只能請(qǐng)求一個(gè)新的布局
            nullLayouts();
            requestLayout();
            invalidate();
        }
    }

說(shuō)實(shí)話,看源碼真的是一件很累的過(guò)程,我們可能很難找到下手的點(diǎn),在這里給大家分享一個(gè)方法,找你覺(jué)得是重點(diǎn)的代碼或者方法去看(和你本次看源碼想要研究的方向相同),一旦你看著看著發(fā)現(xiàn)看不太懂了,你就倒回來(lái)再看其他地方。從上面這段代碼我們不難看出,在這里調(diào)用了requestLayout()invalidate()這兩個(gè)方法,看到這里相信大家應(yīng)該就明白了吧,是他是他就是他,requestLayout(),好,我們也順便來(lái)看一下這個(gè)方法:

TextView:


   public void requestLayout() {
        //判斷是否正在布局
        if (mMeasureCache != null) mMeasureCache.clear();

        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }

        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            //向父容器請(qǐng)求布局
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

requestLayout()方法中調(diào)用了mParent.requestLayout(),也就是說(shuō)調(diào)用了父View的requestLayout()方法,然后一級(jí)一級(jí)往上傳,最終會(huì)調(diào)用ViewRootImpl中的requestLayout()方法:

RootViewImpl:

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

首先會(huì)先去判斷一下是否是在當(dāng)前線程,然后會(huì)調(diào)用scheduleTraversals()方法:

RootViewImpl:

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

看到這里估計(jì)有很多人會(huì)懵逼了,這里好像也沒(méi)有什么嘛,別急老鐵,這里有一個(gè)名叫mTraversalRunnable的參數(shù),那我們就點(diǎn)進(jìn)去看看他的實(shí)現(xiàn):

RootViewImpl:

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

接下來(lái)會(huì)調(diào)用doTraversal()方法:

RootViewImpl:

   void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

然后終于到了我們所期待的地方了,這里又調(diào)用了performTraversals()方法,相信看過(guò)View的繪制流程源碼的童鞋應(yīng)該就知道了,這里才真正開(kāi)始View的測(cè)量,擺放,繪制等操作。在performTraversals()這個(gè)方法中分別調(diào)用了onMeasure,onLayout,onDraw等方法,有興趣的童鞋可以自行去看。至此我們應(yīng)該就能知道上述問(wèn)題該如何回答了。

Q2:為什么TextView的寬高設(shè)置成wrap_content,在Activity中獲取的時(shí)候?qū)挾葹?,高度不為0?
image

這個(gè)問(wèn)題呢,是我在找上面那個(gè)問(wèn)題的答案的過(guò)程中發(fā)現(xiàn)的,既然是寬高的問(wèn)題,那么我們當(dāng)然得要去看onMeasure方法了:

if (widthMode == MeasureSpec.EXACTLY) {
     // Parent has told us how big to be. So be it.
     width = widthSize;
} else {
     //寬度設(shè)置成wrap_content會(huì)走這里
     if (mLayout != null && mEllipsize == null) {
           des = desired(mLayout);
     }

     if (des < 0) {
          boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
          if (boring != null) {
               mBoring = boring;
           }
      } else {
           fromexisting = true;
      }

      if (boring == null || boring == UNKNOWN_BORING) {
          if (des < 0) {
              des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, 0, mTransformed.length(), mTextPaint, mTextDir));
          }
      } else {
           width = boring.width;
      }
      ...
      這下面是計(jì)算設(shè)置drawable和hint的寬度,所以我們可以忽略
}

關(guān)于寬度的我們只需要看這一段就好了,TextView設(shè)置wrap_content,會(huì)走下面的else,然后第一次進(jìn)來(lái),這個(gè)onMeasue里面的mLayout還沒(méi)有初始化,所以mLayout = null,然后由于des = -1,所以boring會(huì)被初始化,boring != null,所以width = boring.width,而boring.width這個(gè)東西初始值為0,所以width = 0;同樣的高度也是這樣分析就可以了,要注意的是,高度和textSize和行數(shù)有關(guān),所以設(shè)置不同的行數(shù)和textSize(默認(rèn)是有TextSize的)得到的hight都不一樣的。

總結(jié):其實(shí)大家可以這樣理解,寬高都設(shè)置成wrap_content,沒(méi)設(shè)置文本的情況下,寬度肯定為0,但是單行的高度是固定的(和TextSize也有關(guān),一旦設(shè)置也是固定的了)。

歡迎到Github上查看全部文章Android-Knowledge

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容