我是靠谱客的博主 雪白龙猫,最近开发中收集的这篇文章主要介绍android textview源码分析,Android TextView源码浅析之Layout,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

OverView

在上一篇从顶层整体流程分析TextView时能看到Layout这个重要概念,无论是onMeasure()过程还是onDraw()过程,主要工作都是由Layout来完成。

Layout类负责的作用是,完成TextView的排版,包括折行、省略等等。

TextView.makeNewLayout()

在上一篇的分析中,在onMeasure()过程,会调用到TextView的makeNewLayout()方法,现在来看下它的实现:

public void makeNewLayout(int wantWidth, int hintWidth,

BoringLayout.Metrics boring,

BoringLayout.Metrics hintBoring,

int ellipsisWidth, boolean bringIntoView) {

// 暂停走马灯动画

stopMarquee();

mOldMaximum = mMaximum;

mOldMaxMode = mMaxMode;

mHighlightPathBogus = true;

if (wantWidth < 0) {

wantWidth = 0;

}

if (hintWidth < 0) {

hintWidth = 0;

}

// 1.获取对齐方式

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.通过上述信息,构建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.构建出用于显示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.重启走马灯动画

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();

}

在这个函数中,最重要的是第4点的通过调用makeSingleLayout()方法构建出主Layout,但第5点构建hint的Layout也很值得学习,因为涉及了BoringLayout和StaticLayout这两个重要的概念:

BoringLayout和StaticLayout都是Layout的子类,其中BoringLayout是指不包含Span而且文本都是左到右而且能够一行展示下的文本,这种情况下使用BoringLayout能够节省不必要的计算;当hint不满足BoringLayout的条件时,会使用StaticLayout来进行布局

TextView.makeSingleLayout()方法

protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,

Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,

boolean useSaved) {

Layout result = null;

// 这个方法的内部会判断文本是否是Spannable,是的话会使用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;

}

}

// 这个条件如果成立,则证明是Boring

if (boring != null) {

// 根据boring的不同属性,创建对应的Layout,if-else分支里都会尝试从mSavedLayout中创建,拿不到再调用BoringLayout.make()来创建新的

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()

在调用StaticLayout.Builder.build()方法之后,最终会调用到StaticLayout的generate()方法来构建出真正对应的Layout,generate()方法很长,还是采取分部分的方式来看:

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部分是对文本缩进的处理,最终处理调用的是native函数实现,但实际这个if代码块,暂时还没有找到能够判断为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是包含了文本测量信息的类,通过它来构建文本能够节省一些开销

接下来的部分,是根据段落信息,逐段地分析段落里的文本内容

for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {

final int paraStart = paraIndex == 0

? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;

final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;

// ……

}

下面这几段的代码都发生在这个大的for循环中

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;

}

}

}

}

然后是行高的分析,通过解析LineHeightSpan来实现

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;

}

}

这一段通过解析TabStopSpan来获取tabStop,排序后存储在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]中取出测量相关的信息,后面准备使用

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;

这一段是根据前述步骤的宽度、行宽等信息,调用native的方法进行了折行处理,处理后的结果在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;

}

这里主要是对末行以及省略的相应处理

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;

}

}

这一段是对段落中的Span和这行进行处理,其中fmCache是前面PrecomputeText在测量时已经测算好的各个Span在top/bottom/ascent/descent这几个维度上的值,并缓存在fmCache中,因此在这里需要计算某个段落的字体属性时,直接从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);

}

注意最后这一段的成立条件:结束为止和起始位置相等,并且前一个字符是换行符,那证明是一个新的空白段落,也需要作为一个单独的段落进行单独处理

StaticLayout.out()

上面已经看完了整体的StaticLayout.generate()函数,其中看到有对out()函数的调用,out()函数完整的签名如下:

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)

参数非常多,但看参数名基本能看出和此前的generate()函数基本是对应的

接下来分段看下它做了什么

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数组是否需要扩容以及在需要时进行扩容操作,mLines数组中存储的是每行的信息,包括每一行的信息包括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部分是对高度进行处理,得到的结果存储在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部分是对省略的计算和处理

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部分是对首行末行的特殊处理,因为要考虑上下留白;还有对行间距的特殊处理

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数组中了

从这里就能看出out函数的作用是对Layout中的每一行文本进行分析,最重要的产出就是得到mLines数组

Layout.draw()

在上一篇TextView的整体流程中我们看到,TextView的onDraw()执行过程中,最终实际上是通过Layout.draw()函数完成的,而在Layout前面的分析中我们已经得到了每一行的信息,所以接下来看下Layout.draw()函数是怎么执行的。

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()实际上分成了两部分,drawBackground()和drawText(),下面看下它们的实现:

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.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);

}

}

代码不长,可以看到Layout的drawBackground()实际上是通过LineBackgroundSpan.drawBackground()来完成的,而LineBackgroundSpan只是一个接口,在Android源码中并没有实现类,因此drawBackground()的实际绘制效果是使用时自己定义的

Layout.drawText()

b22e96d90e42

image.png

drawText()方法整体内容如图,主要的工作在for循环中,从注释中能看出,drawText()的过程是,逐行绘制,下面看下这个for循环内部的逻辑:

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,有的话会调用其drawLeadingMargin()方法来绘制其段首的缩进效果

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,以及对其方式

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部分,根据前面的信息,如果是布局从左到右并且是普通文本而且没有tab而且不需要对齐,则调用canvas.drawText来绘制,否则调用的是TextLine的draw()方法来进行绘制

最后

以上就是雪白龙猫为你收集整理的android textview源码分析,Android TextView源码浅析之Layout的全部内容,希望文章能够帮你解决android textview源码分析,Android TextView源码浅析之Layout所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(33)

评论列表共有 0 条评论

立即
投稿
返回
顶部