一:TextView的onDraw()方法:

1.第一句restartMarqueeIfNeeded()绘制字幕滚动。

protected void onDraw(Canvas canvas) {
restartMarqueeIfNeeded(); // Draw the background for this view
super.onDraw(canvas);
     ...
}

首先我们看一个东西:

android.text.TextUtils.java

public enum TruncateAt {
START,
MIDDLE,
END,
MARQUEE,
/**
* @hide
*/
END_SMALL
}

很熟悉对不对,这就是平常在TextView的android:ellipsize属性,当字符显示不下的时候省略号所在的位置,有开始/结束/中间/滚动四个枚举值。每次onDraw的时候都检测是否需要滚动字幕,重新滚幕的条件就是android:ellipsize属性是MARQUEE(也就是滚动字幕)和mRestartMarquee 布尔值。

private void restartMarqueeIfNeeded() {
if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
mRestartMarquee = false;
startMarquee();
}
}

关于这部分就讲这么多,知道这个是滚动字幕的就行了,若对滚幕感兴趣自行研究canMarquee()/startMarquee()/stopMarquee()/startStopMarquee(boolean start)/Marquee类。

2.compoundDrawable的绘制,也就是drawableTop/Bottom/Left/Right属性。

// Draw the background for this view
super.onDraw(canvas); final int compoundPaddingLeft = getCompoundPaddingLeft();
final int compoundPaddingTop = getCompoundPaddingTop();
final int compoundPaddingRight = getCompoundPaddingRight();
final int compoundPaddingBottom = getCompoundPaddingBottom();
....
final Drawables dr = mDrawables;
if (dr != null) {
/*
* Compound, not extended, because the icon is not clipped
* if the text height is smaller.
*/ int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
// Make sure to update invalidateDrawable() when changing this code.
if (dr.mShowing[Drawables.LEFT] != null) {
canvas.save();
canvas.translate(scrollX + mPaddingLeft + leftOffset,
scrollY + compoundPaddingTop +
(vspace - dr.mDrawableHeightLeft) / 2);
dr.mShowing[Drawables.LEFT].draw(canvas);
canvas.restore();
} // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
// Make sure to update invalidateDrawable() when changing this code.
if (dr.mShowing[Drawables.RIGHT] != null) {
canvas.save();
canvas.translate(scrollX + right - left - mPaddingRight
- dr.mDrawableSizeRight - rightOffset,
scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
dr.mShowing[Drawables.RIGHT].draw(canvas);
canvas.restore();
} // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
// Make sure to update invalidateDrawable() when changing this code.
if (dr.mShowing[Drawables.TOP] != null) {
canvas.save();
canvas.translate(scrollX + compoundPaddingLeft +
(hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
dr.mShowing[Drawables.TOP].draw(canvas);
canvas.restore();
} // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
// Make sure to update invalidateDrawable() when changing this code.
if (dr.mShowing[Drawables.BOTTOM] != null) {
canvas.save();
canvas.translate(scrollX + compoundPaddingLeft +
(hspace - dr.mDrawableWidthBottom) / 2,
scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
dr.mShowing[Drawables.BOTTOM].draw(canvas);
canvas.restore();
}
}
Drawables是TextView下的静态类,持有着mShowing(drawable数组)上下左右四个drawable,这四个drawable绘制在不同的位置。

3.TextPaint和Layout,其实还有mEditor,也就是可编辑状态下的情况(EditText)。这部分先初始化画笔TextPaint,Cavans画布,最重要的就是Layout,由它负责文字绘制。
 Path highlight = getUpdatedHighlightPath();
if (mEditor != null) {
mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
} else {
layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
}

二:Layout类

Layout是android.text下的一个抽象类,负责文字布局绘画,它有两个子类分别是DynamicLayout和StaticLayout,前者是可编辑状态下的(EditText),后者是静态的。

   /**
* Draw this Layout on the specified Canvas.
    绘制在指定的画布
*/
public void draw(Canvas c) {
draw(c, null, null, 0);
} /**
* Draw this Layout on the specified canvas, with the highlight path drawn
* between the background and the text.
在背景和文字之间绘制高亮
*
* @param canvas the canvas
* @param highlight the path of the highlight or cursor; can be null
* @param highlightPaint the paint for the highlight
* @param cursorOffsetVertical the amount to temporarily translate the
* canvas while rendering the highlight
*/
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);
}

1.先看画背景:

public void drawBackground(Canvas canvas, Path highlight, Paint highlightPaint,
int cursorOffsetVertical, int firstLine, int lastLine) {
// First, draw LineBackgroundSpans.//首先,绘制LineBackgroundSpans(不是View的Backgrond哦)
// LineBackgroundSpans know nothing about the alignment, margins, or?/它不需要自动对齐方式,间距或方向
// direction of the layout or line. XXX: Should they?//xxx:需要吗?
// They are evaluated at each line.//将会应用在每一行。
if (mSpannedText) {//SpannedText才能设置Span
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) {//行背景span数量
int previousLineBottom = getLineTop(firstLine);//记录上一行的top
int previousLineEnd = getLineStart(firstLine);//记录上一行的end
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);//下一行的end
previousLineEnd = end; int ltop = previousLineBottom;
int lbottom = getLineTop(i + 1);//获取下一行的top,也就是本行的bottom
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++) {//如果设置了多个LineBackgroundSpan将一一画上
// 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++) {//所有的行数和行背景(line.number*span.number)
LineBackgroundSpan lineBackgroundSpan = (LineBackgroundSpan) spans[n];
lineBackgroundSpan.drawBackground(canvas, paint, 0, width,
ltop, lbaseline, lbottom,
buffer, start, end, i);
}
}
}
mLineBackgroundSpans.recycle();//SpanSet回收
} // There can be a highlight even without spans if we are drawing
// a non-spanned transformation of a spanned editing buffer.
if (highlight != null) {//绘制hightlight路径(比如光标)
if (cursorOffsetVertical != 0) canvas.translate(0, cursorOffsetVertical);
canvas.drawPath(highlight, highlightPaint);
if (cursorOffsetVertical != 0) canvas.translate(0, -cursorOffsetVertical);
}
}

至于lineBottom和linrEnd是由子类(DynamicLayout和StaticLayout)的getLineTop和getLineStart方法获取的,很复杂很复杂。

2.画字drawText:

public void drawText(Canvas canvas, int firstLine, int lastLine) {
int previousLineBottom = getLineTop(firstLine);
int previousLineEnd = getLineStart(firstLine);
ParagraphStyle[] spans = NO_PARA_SPANS;
int spanEnd = 0;
TextPaint paint = mPaint;
CharSequence buf = mText; Alignment paraAlign = mAlignment;
TabStops tabStops = null;
boolean tabStopsIsInitialized = false; TextLine tl = TextLine.obtain(); // Draw the lines, one at a time.
// The baseline is the top of the following line minus the current line's descent.
for (int lineNum = firstLine; lineNum <= lastLine; lineNum++) {//遍历每行
int start = previousLineEnd;//开始
previousLineEnd = getLineStart(lineNum + 1);//记录end
int end = getLineVisibleEnd(lineNum, start, previousLineEnd);//结束 int ltop = previousLineBottom;//行top
int lbottom = getLineTop(lineNum + 1);//行bottom,也就是下一行的top
previousLineBottom = lbottom;//记录行Bottom
int lbaseline = lbottom - getLineDescent(lineNum);//行基线,bottom-descent int dir = getParagraphDirection(lineNum);//段乱排版方向
int left = 0;
int right = mWidth;

       //一:画LeadingMargin
if (mSpannedText) {//是spannedText
Spanned sp = (Spanned) buf;//text
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;
}

          //画出LeadingMarginSpan
// 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
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);
}
}
}
}

       //二:Tab或Emoji
boolean hasTabOrEmoji = getLineContainsTab(lineNum);
// Can't tell if we have tabs for sure, currently
if (hasTabOrEmoji && !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;
}
      
       //四:获取x轴,然后写字。
int x;
if (align == Alignment.ALIGN_NORMAL) {
if (dir == DIR_LEFT_TO_RIGHT) {
x = left + getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
} else {
x = right + getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
}
} else {
int max = (int)getLineExtent(lineNum, tabStops, false);
if (align == Alignment.ALIGN_OPPOSITE) {
if (dir == DIR_LEFT_TO_RIGHT) {
x = right - max + getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
} else {
x = left - max + getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
}
} else { // Alignment.ALIGN_CENTER
max = max & ~1;
x = ((right + left - max) >> 1) +
getIndentAdjust(lineNum, Alignment.ALIGN_CENTER);
}
} paint.setHyphenEdit(getHyphen(lineNum));
Directions directions = getLineDirections(lineNum);
        //阅读方式从左向右的,没有tab和emoji表情,非SpannedText,就是最原始最传统最简单画文字cavans.drawText
if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTabOrEmoji) {
// XXX: assumes there's nothing additional to be done
canvas.drawText(buf, start, end, x, lbaseline, paint);
} else {//复杂的交给TextLine
tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops);
tl.draw(canvas, x, ltop, lbaseline, lbottom);
}
paint.setHyphenEdit(0);
} TextLine.recycle(tl);
}

遍历每一行,主要是由四个流程:画LeadingMargin——>确认tab/emoji(TextLine来画)——>根据对齐方式确定从x轴哪个位置开始画(比如居左x就是0咯)——>

根据条件判断是交给cavans直接drawText还是TextLine来画字。

三:Canvas&TextLine

1.先看Canvas的drawText方法,就看方法doc。

/**
* Draw the specified range of text, specified by start/end, with its
* origin at (x,y), in the specified Paint. The origin is interpreted
* based on the Align setting in the Paint.
*
* @param text The text to be drawn
* @param start The index of the first character in text to draw
* @param end (end - 1) is the index of the last character in text
* to draw
* @param x The x-coordinate of origin for where to draw the text
* @param y The y-coordinate of origin for where to draw the text
* @param paint The paint used for the text (e.g. color, size, style)
*/
public void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
@NonNull Paint paint)

这个注释说了三个点,一是画多少个字(start-end),二是从哪开始画,即原点(origin),这个有xy坐标轴来确定,基于对齐方式的设定,最后就是画笔paint。

说明一下,start和end是从text里面的所以区段,而原点的x轴跟对齐方式相关,y轴一般是baseline。

2.TextLine的draw流程:

void draw(Canvas c, float x, int top, int y, int bottom) {
     //drawRun画字
if (!mHasTabs) {
if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
drawRun(c, 0, mLen, false, x, top, y, bottom, false);
return;
}
if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
drawRun(c, 0, mLen, true, x, top, y, bottom, false);
return;
}
}

     //根据字符转成emoji位图
float h = 0;
int[] runs = mDirections.mDirections;
RectF emojiRect = null; int lastRunIndex = runs.length - 2;
for (int i = 0; i < runs.length; i += 2) {
...for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
int codept = 0;
Bitmap bm = null; ...
bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
...
emojiRect.set(x + h, y + bmAscent,
x + h + width, y);
c.drawBitmap(bm, null, emojiRect, mPaint);
...
}
}
}
}

转入drawRun方法

/**
* Draws a unidirectional (but possibly multi-styled) run of text.
*
*
* @param c the canvas to draw on
* @param start the line-relative start
* @param limit the line-relative limit
* @param runIsRtl true if the run is right-to-left
* @param x the position of the run that is closest to the leading margin
* @param top the top of the line
* @param y the baseline
* @param bottom the bottom of the line
* @param needWidth true if the width value is required.
* @return the signed width of the run, based on the paragraph direction.
* Only valid if needWidth is true.
*/
private float drawRun(Canvas c, int start,
int limit, boolean runIsRtl, float x, int top, int y, int bottom,
boolean needWidth)

而真正的方法还得往下走:

private float handleRun(int start, int measureLimit,
int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
int bottom, FontMetricsInt fmi, boolean needWidth) { // Case of an empty line, make sure we update fmi according to mPaint
     //空行,更新FontMetricsInt
if (start == measureLimit) {
TextPaint wp = mWorkPaint;
wp.set(mPaint);
if (fmi != null) {
expandMetricsFromPaint(fmi, wp);
}
return 0f;
}

     //无mSpanned,直接handleText
if (mSpanned == null) {
TextPaint wp = mWorkPaint;
wp.set(mPaint);
final int mlimit = measureLimit;
return handleText(wp, start, mlimit, start, limit, runIsRtl, c, x, top,
y, bottom, fmi, needWidth || mlimit < measureLimit);
}

     //初始化MetricAffectingSpan和CharacterStyleSpan
mMetricAffectingSpanSpanSet.init(mSpanned, mStart + start, mStart + limit);
mCharacterStyleSpanSet.init(mSpanned, mStart + start, mStart + limit); // Shaping needs to take into account context up to metric boundaries,
// but rendering needs to take into account character style boundaries.
// So we iterate through metric runs to get metric bounds,
// then within each metric run iterate through character style runs
// for the run bounds.
final float originalX = x;
for (int i = start, inext; i < measureLimit; i = inext) {
TextPaint wp = mWorkPaint;
wp.set(mPaint); inext = mMetricAffectingSpanSpanSet.getNextTransition(mStart + i, mStart + limit) -
mStart;
int mlimit = Math.min(inext, measureLimit); ReplacementSpan replacement = null;

        //遍历MetrixAffectingSpan
for (int j = 0; j < mMetricAffectingSpanSpanSet.numberOfSpans; j++) {
// Both intervals [spanStarts..spanEnds] and [mStart + i..mStart + mlimit] are NOT
// empty by construction. This special case in getSpans() explains the >= & <= tests
if ((mMetricAffectingSpanSpanSet.spanStarts[j] >= mStart + mlimit) ||
(mMetricAffectingSpanSpanSet.spanEnds[j] <= mStart + i)) continue;
MetricAffectingSpan span = mMetricAffectingSpanSpanSet.spans[j];
if (span instanceof ReplacementSpan) {//ReplacementSpan特俗处理
replacement = (ReplacementSpan)span;
} else {
// We might have a replacement that uses the draw
// state, otherwise measure state would suffice.
span.updateDrawState(wp);//TextPaint抛出去
}
}

       //处理ReplacementSpan
if (replacement != null) {
x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y,
bottom, fmi, needWidth || mlimit < measureLimit);
continue;
}

       //遍历CharecterStyleSpan
for (int j = i, jnext; j < mlimit; j = jnext) {
jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + mlimit) -
mStart; wp.set(mPaint);
for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
// Intentionally using >= and <= as explained above
if ((mCharacterStyleSpanSet.spanStarts[k] >= mStart + jnext) ||
(mCharacterStyleSpanSet.spanEnds[k] <= mStart + j)) continue; CharacterStyle span = mCharacterStyleSpanSet.spans[k];
span.updateDrawState(wp);//更新draw状态
} // Only draw hyphen on last run in line
if (jnext < mLen) {
wp.setHyphenEdit(0);
}
          //渲染文字...
x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
top, y, bottom, fmi, needWidth || jnext < measureLimit);
}
} return x - originalX;
}

看到这个TextLine主要还是处理SpannedText,遍历出MetricAffectingSpan和CharactStyleSpan,MetricAffectingSpan下面有个ReplacementSpan,其余

的span都是更新draw状态,渲染文字最终还是在handleTextprivate float handleText(TextPaint wp, int start, int end,

int contextStart, int contextEnd, boolean runIsRtl,
Canvas c, float x, int top, int y, int bottom,
FontMetricsInt fmi, boolean needWidth) {
     ... if (c != null) {
if (runIsRtl) {
x -= ret;
} if (wp.bgColor != 0) {//画背景色
int previousColor = wp.getColor();
Paint.Style previousStyle = wp.getStyle(); wp.setColor(wp.bgColor);
wp.setStyle(Paint.Style.FILL);
c.drawRect(x, top, x + ret, bottom, wp); wp.setStyle(previousStyle);
wp.setColor(previousColor);
} if (wp.underlineColor != 0) {//画下划线
// kStdUnderline_Offset = 1/9, defined in SkTextFormatParams.h
          //下划线.top=文字大小的1/9+baseline+baselineShift,也就是说是从baseline空格再往下字符大小的1/9           float underlineTop = y + wp.baselineShift + (1.0f / 9.0f) * wp.getTextSize(); int previousColor = wp.getColor();
Paint.Style previousStyle = wp.getStyle();
boolean previousAntiAlias = wp.isAntiAlias(); wp.setStyle(Paint.Style.FILL);
wp.setAntiAlias(true); wp.setColor(wp.underlineColor);
          //线的粗细,是在TextPaint中定义的
c.drawRect(x, underlineTop, x + ret, underlineTop + wp.underlineThickness, wp); wp.setStyle(previousStyle);
wp.setColor(previousColor);
wp.setAntiAlias(previousAntiAlias);
} drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
x, y + wp.baselineShift);
} return runIsRtl ? -ret : ret;
}

那drawTextRun中的方法是怎么实现的呢?

private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
int contextStart, int contextEnd, boolean runIsRtl, float x, int y) { if (mCharsValid) {
int count = end - start;
int contextCount = contextEnd - contextStart;
c.drawTextRun(mChars, start, count, contextStart, contextCount,
x, y, runIsRtl, wp);
} else {
int delta = mStart;
c.drawTextRun(mText, delta + start, delta + end,
delta + contextStart, delta + contextEnd, x, y, runIsRtl, wp);
}
}

可以看到是调用canvas实现的,canvas都是通过native方法来实现的。

最后上公众号,文章同步,手机阅读。

Android之TextView文字绘制流程的更多相关文章

  1. 深入理解 Android 之 View 的绘制流程

    概述 本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定 ...

  2. 【转】深入理解Android之View的绘制流程

    概述 本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定 ...

  3. Android之View的绘制流程

    本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定实现细 ...

  4. android显示TextView文字的倒影效果

    今天记录一下TextView的倒影效果,显示一串文字,然后在文字的下方显示出它的倒影,先上效果图: 最重要的就是View中getDrawingCache()方法,该方法可以获取cache中的图像,然后 ...

  5. android View层的绘制流程

    还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原理,记不记得 ...

  6. Android View 的添加绘制流程 (二)

    概述 上一篇 Android DecorView 与 Activity 绑定原理分析 分析了在调用 setContentView 之后,DecorView 是如何与 activity 关联在一起的,最 ...

  7. Android GUI之View绘制流程

    在上篇文章中,我们通过跟踪源码,我们了解了Activity.Window.DecorView以及View之间的关系(查看文章:http://www.cnblogs.com/jerehedu/p/460 ...

  8. 【Android】TextView文字长度测量和各种Paddding解析

    老规矩,先上张图 o,这篇好像是分析篇,没有效果图.不管了,位置占着,老规矩不能坏,下面开始正文. *** 这篇博客会讲得比较杂: TextView里各部分的大小该怎么测量? 如何计算每行文字的长度? ...

  9. Android面试,View绘制流程以及invalidate()等相关方法分析

    整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为 根据之前设置的状态,判断是否需要重新计算视图大小(measu ...

随机推荐

  1. Jetty 9嵌入式开发

    官方网址:http://www.eclipse.org/jetty/ 下载地址:http://download.eclipse.org/jetty/stable-9/dist/ 文档网址:http:/ ...

  2. paip.前端加载时间分析之道优化最佳实践

    paip.前端加载时间分析之道优化最佳实践 1.另存为 ,查看文件尺寸..和图片. 2.view the 另存为的htm静态的文件单个的加载,看时间...可以排除编程语言的问题and 数据库.. ## ...

  3. paip.web数据绑定 下拉框的api设计 选择框 uapi python .net java swing jsf总结

    paip.web数据绑定 下拉框的api设计 选择框 uapi  python .net java swing jsf总结 ====总结: 数据绑定下拉框,Uapi 1.最好的是默认绑定..Map(k ...

  4. 搭建Java环境JDK,和运行环境JRE

    1:想要学习Java第一步就是搭建Java环境,就是安装JDK,又因为JDK里面包含JRE,所以在安装JDK的过程中就安装了JRE,所以以下只是给出了JDK的安装包,自行下载安装即可 链接:http: ...

  5. Liferay7 BPM门户开发之30: 通用帮助类Validator、ArrayUtil、StringUtil等使用

    废话不多说,直接上代码. 验证类Validator 主要是空验证.数字.格式验证 调用的例子: protected void validateEmailFrom(ActionRequest actio ...

  6. ATL字符串转换宏

    有比MultiByteToWideChar和WideCharToMultiByte更简单的字符串转换宏,你相信吗?头文件 d:\program files\microsoft visual studi ...

  7. android: 使用 IntentService

    9.5.2 使用 IntentService 话说回来,在本章一开始的时候我们就已经知道,服务中的代码都是默认运行在主线程 当中的,如果直接在服务里去处理一些耗时的逻辑,就很容易出现 ANR(Appl ...

  8. 转:LIRE的使用

    LIRE的使用:创建索引 LIRE(Lucene Image REtrieval)提供一种的简单方式来创建基于图像特性的Lucene索引.利用该索引就能够构建一个基于内容的图像检索(content- ...

  9. 搞了台2ussd服务器

    只上了一颗CPU,内存还没有双通道

  10. quartzScheduler_Worker-1] but has failed to stop it. This is very likely to create a memory leak解决

    01-Jul-2016 07:24:20.218 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 80 ...