Android TextView源碼淺析之Layout

OverView

在上一篇從頂層整體流程分析TextView時(shí)能看到Layout這個(gè)重要概念墓卦,無(wú)論是onMeasure()過(guò)程還是onDraw()過(guò)程,主要工作都是由Layout來(lái)完成漱办。
Layout類負(fù)責(zé)的作用是,完成TextView的排版,包括折行丝格、省略等等聪姿。

TextView.makeNewLayout()

在上一篇的分析中蹦魔,在onMeasure()過(guò)程激率,會(huì)調(diào)用到TextView的makeNewLayout()方法,現(xiàn)在來(lái)看下它的實(shí)現(xiàn):

    public void makeNewLayout(int wantWidth, int hintWidth,
                                 BoringLayout.Metrics boring,
                                 BoringLayout.Metrics hintBoring,
                                 int ellipsisWidth, boolean bringIntoView) {
        // 暫停走馬燈動(dòng)畫
        stopMarquee();

        mOldMaximum = mMaximum;
        mOldMaxMode = mMaxMode;

        mHighlightPathBogus = true;

        if (wantWidth < 0) {
            wantWidth = 0;
        }
        if (hintWidth < 0) {
            hintWidth = 0;
        }

       // 1.獲取對(duì)齊方式
        Layout.Alignment alignment = getLayoutAlignment();
        final boolean testDirChange = mSingleLine && mLayout != null
                && (alignment == Layout.Alignment.ALIGN_NORMAL
                        || alignment == Layout.Alignment.ALIGN_OPPOSITE);
        int oldDir = 0;
        if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
        boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
        // 2.處理省略方式
        final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE
                && mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
        TruncateAt effectiveEllipsize = mEllipsize;
        if (mEllipsize == TruncateAt.MARQUEE
                && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
            effectiveEllipsize = TruncateAt.END_SMALL;
        }
        // 3.獲取文字方向
        if (mTextDir == null) {
            mTextDir = getTextDirectionHeuristic();
        }
        // 4.通過(guò)上述信息勿决,構(gòu)建mLayout
        mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
                effectiveEllipsize, effectiveEllipsize == mEllipsize);
        if (switchEllipsize) {
            TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE
                    ? TruncateAt.END : TruncateAt.MARQUEE;
            mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
                    shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
        }

        shouldEllipsize = mEllipsize != null;
        mHintLayout = null;
        // 5.構(gòu)建出用于顯示hint的Layout
        if (mHint != null) {
            if (shouldEllipsize) hintWidth = wantWidth;

            if (hintBoring == UNKNOWN_BORING) {
                hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
                                                   mHintBoring);
                if (hintBoring != null) {
                    mHintBoring = hintBoring;
                }
            }

            if (hintBoring != null) {
                if (hintBoring.width <= hintWidth
                        && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
                    if (mSavedHintLayout != null) {
                        mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
                                hintBoring, mIncludePad);
                    } else {
                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
                                hintBoring, mIncludePad);
                    }

                    mSavedHintLayout = (BoringLayout) mHintLayout;
                } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
                    if (mSavedHintLayout != null) {
                        mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
                                hintBoring, mIncludePad, mEllipsize,
                                ellipsisWidth);
                    } else {
                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
                                hintBoring, mIncludePad, mEllipsize,
                                ellipsisWidth);
                    }
                }
            }
            if (mHintLayout == null) {
                StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
                        mHint.length(), mTextPaint, hintWidth)
                        .setAlignment(alignment)
                        .setTextDirection(mTextDir)
                        .setLineSpacing(mSpacingAdd, mSpacingMult)
                        .setIncludePad(mIncludePad)
                        .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
                        .setBreakStrategy(mBreakStrategy)
                        .setHyphenationFrequency(mHyphenationFrequency)
                        .setJustificationMode(mJustificationMode)
                        .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
                if (shouldEllipsize) {
                    builder.setEllipsize(mEllipsize)
                            .setEllipsizedWidth(ellipsisWidth);
                }
                mHintLayout = builder.build();
            }
        }

        if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
            registerForPreDraw();
        }
        // 6.重啟走馬燈動(dòng)畫
        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
            if (!compressText(ellipsisWidth)) {
                final int height = mLayoutParams.height;
                if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
                    startMarquee();
                } else {
                    mRestartMarquee = true;
                }
            }
        }

        // CursorControllers need a non-null mLayout
        if (mEditor != null) mEditor.prepareCursorControllers();
    }

在這個(gè)函數(shù)中乒躺,最重要的是第4點(diǎn)的通過(guò)調(diào)用makeSingleLayout()方法構(gòu)建出主Layout,但第5點(diǎn)構(gòu)建hint的Layout也很值得學(xué)習(xí)低缩,因?yàn)樯婕傲薆oringLayout和StaticLayout這兩個(gè)重要的概念:
BoringLayout和StaticLayout都是Layout的子類嘉冒,其中BoringLayout是指不包含Span而且文本都是左到右而且能夠一行展示下的文本,這種情況下使用BoringLayout能夠節(jié)省不必要的計(jì)算咆繁;當(dāng)hint不滿足BoringLayout的條件時(shí)讳推,會(huì)使用StaticLayout來(lái)進(jìn)行布局

TextView.makeSingleLayout()方法

    protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
            Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
            boolean useSaved) {
        Layout result = null;
        // 這個(gè)方法的內(nèi)部會(huì)判斷文本是否是Spannable,是的話會(huì)使用DynamicLayout
        if (useDynamicLayout()) {
            final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint,
                    wantWidth)
                    .setDisplayText(mTransformed)
                    .setAlignment(alignment)
                    .setTextDirection(mTextDir)
                    .setLineSpacing(mSpacingAdd, mSpacingMult)
                    .setIncludePad(mIncludePad)
                    .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
                    .setBreakStrategy(mBreakStrategy)
                    .setHyphenationFrequency(mHyphenationFrequency)
                    .setJustificationMode(mJustificationMode)
                    .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
                    .setEllipsizedWidth(ellipsisWidth);
            result = builder.build();
        } else {
            // 判斷是否確定了是不是Boring
            if (boring == UNKNOWN_BORING) {
                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
                if (boring != null) {
                    mBoring = boring;
                }
            }
            // 這個(gè)條件如果成立玩般,則證明是Boring
            if (boring != null) {
                // 根據(jù)boring的不同屬性银觅,創(chuàng)建對(duì)應(yīng)的Layout,if-else分支里都會(huì)嘗試從mSavedLayout中創(chuàng)建坏为,拿不到再調(diào)用BoringLayout.make()來(lái)創(chuàng)建新的
                if (boring.width <= wantWidth
                        && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
                    if (useSaved && mSavedLayout != null) {
                        result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
                                boring, mIncludePad);
                    } else {
                        result = BoringLayout.make(mTransformed, mTextPaint,
                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
                                boring, mIncludePad);
                    }

                    if (useSaved) {
                        mSavedLayout = (BoringLayout) result;
                    }
                } else if (shouldEllipsize && boring.width <= wantWidth) {
                    if (useSaved && mSavedLayout != null) {
                        result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
                                boring, mIncludePad, effectiveEllipsize,
                                ellipsisWidth);
                    } else {
                        result = BoringLayout.make(mTransformed, mTextPaint,
                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
                                boring, mIncludePad, effectiveEllipsize,
                                ellipsisWidth);
                    }
                }
            }
        }
        // 在這里result依然為null究驴,證明不是Boring,需要用StaticLayout
        if (result == null) {
            StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
                    0, mTransformed.length(), mTextPaint, wantWidth)
                    .setAlignment(alignment)
                    .setTextDirection(mTextDir)
                    .setLineSpacing(mSpacingAdd, mSpacingMult)
                    .setIncludePad(mIncludePad)
                    .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
                    .setBreakStrategy(mBreakStrategy)
                    .setHyphenationFrequency(mHyphenationFrequency)
                    .setJustificationMode(mJustificationMode)
                    .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
            if (shouldEllipsize) {
                builder.setEllipsize(effectiveEllipsize)
                        .setEllipsizedWidth(ellipsisWidth);
            }
            result = builder.build();
        }
        return result;
    }

StaticLayout.generate()

在調(diào)用StaticLayout.Builder.build()方法之后匀伏,最終會(huì)調(diào)用到StaticLayout的generate()方法來(lái)構(gòu)建出真正對(duì)應(yīng)的Layout洒忧,generate()方法很長(zhǎng),還是采取分部分的方式來(lái)看:

    final CharSequence source = b.mText;
        final int bufStart = b.mStart;
        final int bufEnd = b.mEnd;
        TextPaint paint = b.mPaint;
        int outerWidth = b.mWidth;
        TextDirectionHeuristic textDir = b.mTextDir;
        final boolean fallbackLineSpacing = b.mFallbackLineSpacing;
        float spacingmult = b.mSpacingMult;
        float spacingadd = b.mSpacingAdd;
        float ellipsizedWidth = b.mEllipsizedWidth;
        TextUtils.TruncateAt ellipsize = b.mEllipsize;
        final boolean addLastLineSpacing = b.mAddLastLineLineSpacing;
        LineBreaks lineBreaks = new LineBreaks(); 
        FloatArray widths = new FloatArray();

        mLineCount = 0;
        mEllipsized = false;
        mMaxLineHeight = mMaximumVisibleLineCount < 1 ? 0 : DEFAULT_MAX_LINE_HEIGHT;

        int v = 0;
        boolean needMultiply = (spacingmult != 1 || spacingadd != 0);

        Paint.FontMetricsInt fm = b.mFontMetricsInt;
        int[] chooseHtv = null;
  • 第1部分是變量的初始化
    final int[] indents;
        if (mLeftIndents != null || mRightIndents != null) {
            final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
            final int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
            final int indentsLen = Math.max(leftLen, rightLen);
            indents = new int[indentsLen];
            for (int i = 0; i < leftLen; i++) {
                indents[i] = mLeftIndents[i];
            }
            for (int i = 0; i < rightLen; i++) {
                indents[i] += mRightIndents[i];
            }
        } else {
            indents = null;
        }

        final long nativePtr = nInit(
                b.mBreakStrategy, b.mHyphenationFrequency,
                b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
                indents, mLeftPaddings, mRightPaddings);
  • 第2部分是對(duì)文本縮進(jìn)的處理够颠,最終處理調(diào)用的是native函數(shù)實(shí)現(xiàn)熙侍,但實(shí)際這個(gè)if代碼塊,暫時(shí)還沒(méi)有找到能夠判斷為true的地方
        PrecomputedText.ParagraphInfo[] paragraphInfo = null;
        final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null;
        if (source instanceof PrecomputedText) {
            PrecomputedText precomputed = (PrecomputedText) source;
            if (precomputed.canUseMeasuredResult(bufStart, bufEnd, textDir, paint,
                      b.mBreakStrategy, b.mHyphenationFrequency)) {
                paragraphInfo = precomputed.getParagraphInfo();
            }
        }

        if (paragraphInfo == null) {
            final PrecomputedText.Params param = new PrecomputedText.Params(paint, textDir,
                    b.mBreakStrategy, b.mHyphenationFrequency);
            paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
                    bufEnd, false);
        }
  • 第3部分是分析文本的段落信息履磨,這里涉及到的PrecomputedText是包含了文本測(cè)量信息的類蛉抓,通過(guò)它來(lái)構(gòu)建文本能夠節(jié)省一些開銷

接下來(lái)的部分,是根據(jù)段落信息剃诅,逐段地分析段落里的文本內(nèi)容

       for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {
                final int paraStart = paraIndex == 0
                        ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
                final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
        // ……
        }

下面這幾段的代碼都發(fā)生在這個(gè)大的for循環(huán)中

                int firstWidthLineCount = 1;
                int firstWidth = outerWidth;
                int restWidth = outerWidth;

                LineHeightSpan[] chooseHt = null;

                if (spanned != null) {
                    LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
                            LeadingMarginSpan.class);
                    for (int i = 0; i < sp.length; i++) {
                        LeadingMarginSpan lms = sp[i];
                        firstWidth -= sp[i].getLeadingMargin(true);
                        restWidth -= sp[i].getLeadingMargin(false);

                        // LeadingMarginSpan2 is odd.  The count affects all
                        // leading margin spans, not just this particular one
                        if (lms instanceof LeadingMarginSpan2) {
                            LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
                            firstWidthLineCount = Math.max(firstWidthLineCount,
                                    lms2.getLeadingMarginLineCount());
                        }
                    }

  • 首先是寬度方面的分析芝雪,解析LeadingMarginSpan和LeadingMargin2
                    chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);

                    if (chooseHt.length == 0) {
                        chooseHt = null; // So that out() would not assume it has any contents
                    } else {
                        if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
                            chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
                        }

                        for (int i = 0; i < chooseHt.length; i++) {
                            int o = spanned.getSpanStart(chooseHt[i]);

                            if (o < paraStart) {
                                // starts in this layout, before the
                                // current paragraph

                                chooseHtv[i] = getLineTop(getLineForOffset(o));
                            } else {
                                // starts in this paragraph

                                chooseHtv[i] = v;
                            }
                        }
                    }
                }
  • 然后是行高的分析,通過(guò)解析LineHeightSpan來(lái)實(shí)現(xiàn)
                int[] variableTabStops = null;
                if (spanned != null) {
                    TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
                            paraEnd, TabStopSpan.class);
                    if (spans.length > 0) {
                        int[] stops = new int[spans.length];
                        for (int i = 0; i < spans.length; i++) {
                            stops[i] = spans[i].getTabStop();
                        }
                        Arrays.sort(stops, 0, stops.length);
                        variableTabStops = stops;
                    }
                }
  • 這一段通過(guò)解析TabStopSpan來(lái)獲取tabStop综苔,排序后存儲(chǔ)在variableTabStop中
                final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
                final char[] chs = measuredPara.getChars();
                final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
                final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
                widths.resize(chs.length);
  • 這部分作用是從段落信息paragraphInfo[paraIndex]中取出測(cè)量相關(guān)的信息惩系,后面準(zhǔn)備使用
               int breakCount = nComputeLineBreaks(
                        nativePtr,

                        // Inputs
                        chs,
                        measuredPara.getNativePtr(),
                        paraEnd - paraStart,
                        firstWidth,
                        firstWidthLineCount,
                        restWidth,
                        variableTabStops,
                        TAB_INCREMENT,
                        mLineCount,

                        // Outputs
                        lineBreaks,
                        lineBreaks.breaks.length,
                        lineBreaks.breaks,
                        lineBreaks.widths,
                        lineBreaks.ascents,
                        lineBreaks.descents,
                        lineBreaks.flags,
                        widths.getRawArray());

                final int[] breaks = lineBreaks.breaks;
                final float[] lineWidths = lineBreaks.widths;
                final float[] ascents = lineBreaks.ascents;
                final float[] descents = lineBreaks.descents;
                final int[] flags = lineBreaks.flags;
  • 這一段是根據(jù)前述步驟的寬度、行寬等信息如筛,調(diào)用native的方法進(jìn)行了折行處理堡牡,處理后的結(jié)果在lineBreaks中
                final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
                final boolean ellipsisMayBeApplied = ellipsize != null
                        && (ellipsize == TextUtils.TruncateAt.END
                            || (mMaximumVisibleLineCount == 1
                                    && ellipsize != TextUtils.TruncateAt.MARQUEE));
                if (0 < remainingLineCount && remainingLineCount < breakCount
                        && ellipsisMayBeApplied) {
                    // Calculate width and flag.
                    float width = 0;
                    int flag = 0; // XXX May need to also have starting hyphen edit
                    for (int i = remainingLineCount - 1; i < breakCount; i++) {
                        if (i == breakCount - 1) {
                            width += lineWidths[i];
                        } else {
                            for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
                                width += widths.get(j);
                            }
                        }
                        flag |= flags[i] & TAB_MASK;
                    }
                    // Treat the last line and overflowed lines as a single line.
                    breaks[remainingLineCount - 1] = breaks[breakCount - 1];
                    lineWidths[remainingLineCount - 1] = width;
                    flags[remainingLineCount - 1] = flag;

                    breakCount = remainingLineCount;
                }
  • 這里主要是對(duì)末行以及省略的相應(yīng)處理
                int here = paraStart;

                int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
                int fmCacheIndex = 0;
                int spanEndCacheIndex = 0;
                int breakIndex = 0;
                for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
                    // retrieve end of span
                    spanEnd = spanEndCache[spanEndCacheIndex++];

                    // retrieve cached metrics, order matches above
                    fm.top = fmCache[fmCacheIndex * 4 + 0];
                    fm.bottom = fmCache[fmCacheIndex * 4 + 1];
                    fm.ascent = fmCache[fmCacheIndex * 4 + 2];
                    fm.descent = fmCache[fmCacheIndex * 4 + 3];
                    fmCacheIndex++;

                    if (fm.top < fmTop) {
                        fmTop = fm.top;
                    }
                    if (fm.ascent < fmAscent) {
                        fmAscent = fm.ascent;
                    }
                    if (fm.descent > fmDescent) {
                        fmDescent = fm.descent;
                    }
                    if (fm.bottom > fmBottom) {
                        fmBottom = fm.bottom;
                    }

                    // skip breaks ending before current span range
                    while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
                        breakIndex++;
                    }

                    while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
                        int endPos = paraStart + breaks[breakIndex];

                        boolean moreChars = (endPos < bufEnd);

                        final int ascent = fallbackLineSpacing
                                ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
                                : fmAscent;
                        final int descent = fallbackLineSpacing
                                ? Math.max(fmDescent, Math.round(descents[breakIndex]))
                                : fmDescent;
                        v = out(source, here, endPos,
                                ascent, descent, fmTop, fmBottom,
                                v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
                                flags[breakIndex], needMultiply, measuredPara, bufEnd,
                                includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(),
                                paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
                                paint, moreChars);

                        if (endPos < spanEnd) {
                            // preserve metrics for current span
                            fmTop = fm.top;
                            fmBottom = fm.bottom;
                            fmAscent = fm.ascent;
                            fmDescent = fm.descent;
                        } else {
                            fmTop = fmBottom = fmAscent = fmDescent = 0;
                        }

                        here = endPos;
                        breakIndex++;

                        if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
                            return;
                        }
                    }
                }

                if (paraEnd == bufEnd) {
                    break;
                }
            }
  • 這一段是對(duì)段落中的Span和這行進(jìn)行處理,其中fmCache是前面PrecomputeText在測(cè)量時(shí)已經(jīng)測(cè)算好的各個(gè)Span在top/bottom/ascent/descent這幾個(gè)維度上的值杨刨,并緩存在fmCache中晤柄,因此在這里需要計(jì)算某個(gè)段落的字體屬性時(shí),直接從fmCache中取出即可
if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
                    && mLineCount < mMaximumVisibleLineCount) {
                final MeasuredParagraph measuredPara =
                        MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
                paint.getFontMetricsInt(fm);
                v = out(source,
                        bufEnd, bufEnd, fm.ascent, fm.descent,
                        fm.top, fm.bottom,
                        v,
                        spacingmult, spacingadd, null,
                        null, fm, 0,
                        needMultiply, measuredPara, bufEnd,
                        includepad, trackpad, addLastLineSpacing, null,
                        null, bufStart, ellipsize,
                        ellipsizedWidth, 0, paint, false);
            }
  • 注意最后這一段的成立條件:結(jié)束為止和起始位置相等妖胀,并且前一個(gè)字符是換行符芥颈,那證明是一個(gè)新的空白段落惠勒,也需要作為一個(gè)單獨(dú)的段落進(jìn)行單獨(dú)處理

StaticLayout.out()

上面已經(jīng)看完了整體的StaticLayout.generate()函數(shù),其中看到有對(duì)out()函數(shù)的調(diào)用爬坑,out()函數(shù)完整的簽名如下:

private int out(final CharSequence text, final int start, final int end, int above, int below,
            int top, int bottom, int v, final float spacingmult, final float spacingadd,
            final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
            final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured,
            final int bufEnd, final boolean includePad, final boolean trackPad,
            final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
            final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
            final float textWidth, final TextPaint paint, final boolean moreChars)

參數(shù)非常多纠屋,但看參數(shù)名基本能看出和此前的generate()函數(shù)基本是對(duì)應(yīng)的
接下來(lái)分段看下它做了什么

        final int j = mLineCount;
        final int off = j * mColumns;
        final int want = off + mColumns + TOP;
        int[] lines = mLines;
        final int dir = measured.getParagraphDir();

        if (want >= lines.length) {
            final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
            System.arraycopy(lines, 0, grow, 0, lines.length);
            mLines = grow;
            lines = grow;
        }

        if (j >= mLineDirections.length) {
            final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
                    GrowingArrayUtils.growSize(j));
            System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
            mLineDirections = grow;
        }
  • 第1部分是判斷mLines/mLineDirections數(shù)組是否需要擴(kuò)容以及在需要時(shí)進(jìn)行擴(kuò)容操作,mLines數(shù)組中存儲(chǔ)的是每行的信息盾计,包括每一行的信息包括START/TOP/DESCENT/HYPHEN/ELLIPSIS_START/ELLIPSIS_COUNT
        if (chooseHt != null) {
            fm.ascent = above;
            fm.descent = below;
            fm.top = top;
            fm.bottom = bottom;

            for (int i = 0; i < chooseHt.length; i++) {
                if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
                    ((LineHeightSpan.WithDensity) chooseHt[i])
                            .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
                } else {
                    chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
                }
            }

            above = fm.ascent;
            below = fm.descent;
            top = fm.top;
            bottom = fm.bottom;
        }

第2部分是對(duì)高度進(jìn)行處理售担,得到的結(jié)果存儲(chǔ)在above/below/top/bottom變量中

        if (ellipsize != null) {
            // If there is only one line, then do any type of ellipsis except when it is MARQUEE
            // if there are multiple lines, just allow END ellipsis on the last line
            boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);

            boolean doEllipsis =
                    (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
                            ellipsize != TextUtils.TruncateAt.MARQUEE) ||
                    (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
                            ellipsize == TextUtils.TruncateAt.END);
            if (doEllipsis) {
                calculateEllipsis(start, end, widths, widthStart,
                        ellipsisWidth, ellipsize, j,
                        textWidth, paint, forceEllipsis);
            }
        }
  • 第3部分是對(duì)省略的計(jì)算和處理
        final boolean lastLine;
        if (mEllipsized) {
            lastLine = true;
        } else {
            final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
                    && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
            if (end == bufEnd && !lastCharIsNewLine) {
                lastLine = true;
            } else if (start == bufEnd && lastCharIsNewLine) {
                lastLine = true;
            } else {
                lastLine = false;
            }
        }

        if (firstLine) {
            if (trackPad) {
                mTopPadding = top - above;
            }

            if (includePad) {
                above = top;
            }
        }

        int extra;

        if (lastLine) {
            if (trackPad) {
                mBottomPadding = bottom - below;
            }

            if (includePad) {
                below = bottom;
            }
        }

        if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
            double ex = (below - above) * (spacingmult - 1) + spacingadd;
            if (ex >= 0) {
                extra = (int)(ex + EXTRA_ROUNDING);
            } else {
                extra = -(int)(-ex + EXTRA_ROUNDING);
            }
        } else {
            extra = 0;
        }
  • 第4部分是對(duì)首行末行的特殊處理,因?yàn)橐紤]上下留白署辉;還有對(duì)行間距的特殊處理
      lines[off + START] = start;
        lines[off + TOP] = v;
        lines[off + DESCENT] = below + extra;
        lines[off + EXTRA] = extra;

        // special case for non-ellipsized last visible line when maxLines is set
        // store the height as if it was ellipsized
        if (!mEllipsized && currentLineIsTheLastVisibleOne) {
            // below calculation as if it was the last line
            int maxLineBelow = includePad ? bottom : below;
            // similar to the calculation of v below, without the extra.
            mMaxLineHeight = v + (maxLineBelow - above);
        }

        v += (below - above) + extra;
        lines[off + mColumns + START] = end;
        lines[off + mColumns + TOP] = v;

        lines[off + TAB] |= flags & TAB_MASK;
        lines[off + HYPHEN] = flags;
        lines[off + DIR] |= dir << DIR_SHIFT;
        mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);

        mLineCount++;
        return v;
  • 第5部分就是將處理完的每一行的信息族铆,都記錄到lines數(shù)組中了

從這里就能看出out函數(shù)的作用是對(duì)Layout中的每一行文本進(jìn)行分析,最重要的產(chǎn)出就是得到mLines數(shù)組

Layout.draw()

在上一篇TextView的整體流程中我們看到哭尝,TextView的onDraw()執(zhí)行過(guò)程中哥攘,最終實(shí)際上是通過(guò)Layout.draw()函數(shù)完成的,而在Layout前面的分析中我們已經(jīng)得到了每一行的信息材鹦,所以接下來(lái)看下Layout.draw()函數(shù)是怎么執(zhí)行的逝淹。

    public void draw(Canvas canvas, Path highlight, Paint highlightPaint,
            int cursorOffsetVertical) {
        final long lineRange = getLineRangeForDraw(canvas);
        int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
        int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
        if (lastLine < 0) return;

        drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical,
                firstLine, lastLine);
        drawText(canvas, firstLine, lastLine);
    }

Layout.draw()實(shí)際上分成了兩部分,drawBackground()和drawText()侠姑,下面看下它們的實(shí)現(xiàn):

Layout.drawBackground()

    public void drawBackground(Canvas canvas, Path highlight, Paint highlightPaint,
            int cursorOffsetVertical, int firstLine, int lastLine) {
        // First, draw LineBackgroundSpans.
        // LineBackgroundSpans know nothing about the alignment, margins, or
        // direction of the layout or line.  XXX: Should they?
        // They are evaluated at each line.
        if (mSpannedText) {
            if (mLineBackgroundSpans == null) {
                mLineBackgroundSpans = new SpanSet<LineBackgroundSpan>(LineBackgroundSpan.class);
            }

            Spanned buffer = (Spanned) mText;
            int textLength = buffer.length();
            mLineBackgroundSpans.init(buffer, 0, textLength);

            if (mLineBackgroundSpans.numberOfSpans > 0) {
                int previousLineBottom = getLineTop(firstLine);
                int previousLineEnd = getLineStart(firstLine);
                ParagraphStyle[] spans = NO_PARA_SPANS;
                int spansLength = 0;
                TextPaint paint = mPaint;
                int spanEnd = 0;
                final int width = mWidth;
                for (int i = firstLine; i <= lastLine; i++) {
                    int start = previousLineEnd;
                    int end = getLineStart(i + 1);
                    previousLineEnd = end;

                    int ltop = previousLineBottom;
                    int lbottom = getLineTop(i + 1);
                    previousLineBottom = lbottom;
                    int lbaseline = lbottom - getLineDescent(i);

                    if (start >= spanEnd) {
                        // These should be infrequent, so we'll use this so that
                        // we don't have to check as often.
                        spanEnd = mLineBackgroundSpans.getNextTransition(start, textLength);
                        // All LineBackgroundSpans on a line contribute to its background.
                        spansLength = 0;
                        // Duplication of the logic of getParagraphSpans
                        if (start != end || start == 0) {
                            // Equivalent to a getSpans(start, end), but filling the 'spans' local
                            // array instead to reduce memory allocation
                            for (int j = 0; j < mLineBackgroundSpans.numberOfSpans; j++) {
                                // equal test is valid since both intervals are not empty by
                                // construction
                                if (mLineBackgroundSpans.spanStarts[j] >= end ||
                                        mLineBackgroundSpans.spanEnds[j] <= start) continue;
                                spans = GrowingArrayUtils.append(
                                        spans, spansLength, mLineBackgroundSpans.spans[j]);
                                spansLength++;
                            }
                        }
                    }

                    for (int n = 0; n < spansLength; n++) {
                        LineBackgroundSpan lineBackgroundSpan = (LineBackgroundSpan) spans[n];
                        lineBackgroundSpan.drawBackground(canvas, paint, 0, width,
                                ltop, lbaseline, lbottom,
                                buffer, start, end, i);
                    }
                }
            }
            mLineBackgroundSpans.recycle();
        }
        // There can be a highlight even without spans if we are drawing
        // a non-spanned transformation of a spanned editing buffer.
        if (highlight != null) {
            if (cursorOffsetVertical != 0) canvas.translate(0, cursorOffsetVertical);
            canvas.drawPath(highlight, highlightPaint);
            if (cursorOffsetVertical != 0) canvas.translate(0, -cursorOffsetVertical);
        }
    }
  • 代碼不長(zhǎng),可以看到Layout的drawBackground()實(shí)際上是通過(guò)LineBackgroundSpan.drawBackground()來(lái)完成的箩做,而LineBackgroundSpan只是一個(gè)接口莽红,在Android源碼中并沒(méi)有實(shí)現(xiàn)類,因此drawBackground()的實(shí)際繪制效果是使用時(shí)自己定義的

Layout.drawText()

image.png

drawText()方法整體內(nèi)容如圖邦邦,主要的工作在for循環(huán)中安吁,從注釋中能看出,drawText()的過(guò)程是燃辖,逐行繪制鬼店,下面看下這個(gè)for循環(huán)內(nèi)部的邏輯:

            int start = previousLineEnd;
            previousLineEnd = getLineStart(lineNum + 1);
            final boolean justify = isJustificationRequired(lineNum);
            int end = getLineVisibleEnd(lineNum, start, previousLineEnd);
            paint.setHyphenEdit(getHyphen(lineNum));

            int ltop = previousLineBottom;
            int lbottom = getLineTop(lineNum + 1);
            previousLineBottom = lbottom;
            int lbaseline = lbottom - getLineDescent(lineNum);

            int dir = getParagraphDirection(lineNum);
            int left = 0;
            int right = mWidth;

            if (mSpannedText) {
                Spanned sp = (Spanned) buf;
                int textLength = buf.length();
                boolean isFirstParaLine = (start == 0 || buf.charAt(start - 1) == '\n');

                // New batch of paragraph styles, collect into spans array.
                // Compute the alignment, last alignment style wins.
                // Reset tabStops, we'll rebuild if we encounter a line with
                // tabs.
                // We expect paragraph spans to be relatively infrequent, use
                // spanEnd so that we can check less frequently.  Since
                // paragraph styles ought to apply to entire paragraphs, we can
                // just collect the ones present at the start of the paragraph.
                // If spanEnd is before the end of the paragraph, that's not
                // our problem.
                if (start >= spanEnd && (lineNum == firstLine || isFirstParaLine)) {
                    spanEnd = sp.nextSpanTransition(start, textLength,
                                                    ParagraphStyle.class);
                    spans = getParagraphSpans(sp, start, spanEnd, ParagraphStyle.class);

                    paraAlign = mAlignment;
                    for (int n = spans.length - 1; n >= 0; n--) {
                        if (spans[n] instanceof AlignmentSpan) {
                            paraAlign = ((AlignmentSpan) spans[n]).getAlignment();
                            break;
                        }
                    }

                    tabStopsIsInitialized = false;
                }

                // Draw all leading margin spans.  Adjust left or right according
                // to the paragraph direction of the line.
                final int length = spans.length;
                boolean useFirstLineMargin = isFirstParaLine;
                for (int n = 0; n < length; n++) {
                    if (spans[n] instanceof LeadingMarginSpan2) {
                        int count = ((LeadingMarginSpan2) spans[n]).getLeadingMarginLineCount();
                        int startLine = getLineForOffset(sp.getSpanStart(spans[n]));
                        // if there is more than one LeadingMarginSpan2, use
                        // the count that is greatest
                        if (lineNum < startLine + count) {
                            useFirstLineMargin = true;
                            break;
                        }
                    }
                }
                for (int n = 0; n < length; n++) {
                    if (spans[n] instanceof LeadingMarginSpan) {
                        LeadingMarginSpan margin = (LeadingMarginSpan) spans[n];
                        if (dir == DIR_RIGHT_TO_LEFT) {
                            margin.drawLeadingMargin(canvas, paint, right, dir, ltop,
                                                     lbaseline, lbottom, buf,
                                                     start, end, isFirstParaLine, this);
                            right -= margin.getLeadingMargin(useFirstLineMargin);
                        } else {
                            margin.drawLeadingMargin(canvas, paint, left, dir, ltop,
                                                     lbaseline, lbottom, buf,
                                                     start, end, isFirstParaLine, this);
                            left += margin.getLeadingMargin(useFirstLineMargin);
                        }
                    }
                }
            }
  • 第1部分,主要是判斷是不是Spanned黔龟,是的話需要獲取影響ParagraphStyle的Span妇智,然后判斷其中是否有LeadingMarginSpan,有的話會(huì)調(diào)用其drawLeadingMargin()方法來(lái)繪制其段首的縮進(jìn)效果
            boolean hasTab = getLineContainsTab(lineNum);
            // Can't tell if we have tabs for sure, currently
            if (hasTab && !tabStopsIsInitialized) {
                if (tabStops == null) {
                    tabStops = new TabStops(TAB_INCREMENT, spans);
                } else {
                    tabStops.reset(TAB_INCREMENT, spans);
                }
                tabStopsIsInitialized = true;
            }

            // Determine whether the line aligns to normal, opposite, or center.
            Alignment align = paraAlign;
            if (align == Alignment.ALIGN_LEFT) {
                align = (dir == DIR_LEFT_TO_RIGHT) ?
                    Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE;
            } else if (align == Alignment.ALIGN_RIGHT) {
                align = (dir == DIR_LEFT_TO_RIGHT) ?
                    Alignment.ALIGN_OPPOSITE : Alignment.ALIGN_NORMAL;
            }

            int x;
            final int indentWidth;
            if (align == Alignment.ALIGN_NORMAL) {
                if (dir == DIR_LEFT_TO_RIGHT) {
                    indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
                    x = left + indentWidth;
                } else {
                    indentWidth = -getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
                    x = right - indentWidth;
                }
            } else {
                int max = (int)getLineExtent(lineNum, tabStops, false);
                if (align == Alignment.ALIGN_OPPOSITE) {
                    if (dir == DIR_LEFT_TO_RIGHT) {
                        indentWidth = -getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
                        x = right - max - indentWidth;
                    } else {
                        indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
                        x = left - max + indentWidth;
                    }
                } else { // Alignment.ALIGN_CENTER
                    indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_CENTER);
                    max = max & ~1;
                    x = ((right + left - max) >> 1) + indentWidth;
                }
            }

            Directions directions = getLineDirections(lineNum);
  • 第2部分氏身,確定該行中是否有Tab巍棱,以及對(duì)其方式
           if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTab && !justify) {
                // XXX: assumes there's nothing additional to be done
                canvas.drawText(buf, start, end, x, lbaseline, paint);
            } else {
                tl.set(paint, buf, start, end, dir, directions, hasTab, tabStops);
                if (justify) {
                    tl.justify(right - left - indentWidth);
                }
                tl.draw(canvas, x, ltop, lbaseline, lbottom);
            }
  • 第3部分,根據(jù)前面的信息蛋欣,如果是布局從左到右并且是普通文本而且沒(méi)有tab而且不需要對(duì)齊航徙,則調(diào)用canvas.drawText來(lái)繪制,否則調(diào)用的是TextLine的draw()方法來(lái)進(jìn)行繪制
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末陷虎,一起剝皮案震驚了整個(gè)濱河市到踏,隨后出現(xiàn)的幾起案子杠袱,更是在濱河造成了極大的恐慌,老刑警劉巖窝稿,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件楣富,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡讹躯,警方通過(guò)查閱死者的電腦和手機(jī)菩彬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)潮梯,“玉大人骗灶,你說(shuō)我怎么就攤上這事”螅” “怎么了耙旦?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)萝究。 經(jīng)常有香客問(wèn)我免都,道長(zhǎng),這世上最難降的妖魔是什么帆竹? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任绕娘,我火速辦了婚禮,結(jié)果婚禮上栽连,老公的妹妹穿的比我還像新娘险领。我一直安慰自己,他們只是感情好秒紧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布绢陌。 她就那樣靜靜地躺著,像睡著了一般熔恢。 火紅的嫁衣襯著肌膚如雪脐湾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天叙淌,我揣著相機(jī)與錄音秤掌,去河邊找鬼。 笑死鹰霍,一個(gè)胖子當(dāng)著我的面吹牛机杜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播衅谷,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼椒拗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蚀苛,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤在验,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后堵未,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腋舌,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年渗蟹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了块饺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡雌芽,死狀恐怖授艰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情世落,我是刑警寧澤淮腾,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站屉佳,受9級(jí)特大地震影響谷朝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜武花,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一圆凰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧体箕,春花似錦专钉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)炮沐。三九已至争群,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間大年,已是汗流浹背换薄。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留翔试,地道東北人轻要。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像垦缅,于是被迫代替她去往敵國(guó)和親冲泥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355