Android之TextView文字绘制流程

一: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方法来实现的。

时间: 2024-10-13 17:51:23

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

Android自定义控件_View的绘制流程

每一个View/ViewGroup的显示都会经过三个过程:1.measure过程(测量View显示的大小,位置):2.layout过程(布局view的位置):3.draw过程(上一篇文章说到的通过canvas绘制到界面上显示,形成了各色的View) 下面分析一下各个过程:measure过程: 因为DecorView实际上是派生自FrameLayout的类,也即一个ViewGroup实例,该ViewGroup内部的ContentViews又是一个ViewGroup实例,依次内嵌View或ViewG

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

概述 本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定实现细节则可以日后再对相应源码进行研读.在进行实际的分析之前,我们先来看下面这张图: 我们来对上图做出简单解释:DecorView是一个应用窗口的根容器,它本质上是一个FrameLayout.DecorView有唯一一个子View,它是一个垂直LinearLayout,包含两个子元素,一个是Titl

Android GUI之View绘制流程

在上篇文章中,我们通过跟踪源码,我们了解了Activity.Window.DecorView以及View之间的关系(查看文章:http://blog.csdn.net/jerehedu/article/details/47021541).那么整个Activity的界面到底是如何绘制出来的呢?既然DecorView作为Activity的顶层界面视图,那么整个界面的绘制工作应该从它开始,下面我们继续跟踪源码,看看是不是这样的. Activity在启动过程中会调用主线程ActivityThread中的

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

整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为 根据之前设置的状态,判断是否需要重新计算视图大小(measure).是否重新需要安置视图的位置(layout).以及是否需要重绘 (draw),其框架过程如下: measure()过程 主要作用:为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth),每个View的控件的实际宽高

[05] Android View 测量-布局-绘制流程

Android中View绘制流程分析

创建Window 在Activity的attach方法中通过调用PolicyManager.makeNewWindo创建Window,将一个View add到WindowManager时,WindowManagerImpl创建一个ViewRoot来管理该窗口的根View.并通过ViewRoot.setView方法把该View传给ViewRoot. final void attach(Context context, ActivityThread aThread, Instrumentation

TextView文字描边实现

TextView文字描边实现 需求描述 文字显示在图片的上面,图片的内容是不确定了,为了防止文字与图片的颜色相近导致用户看不到或者看不清文字的问题,所以显示文字描边,避免问题. 实现 实现思想 使用TextPaint绘制相同文字在TextView的底部,TextPaint的字显示要比原始的字大一些,这样看起来就像是有描边的文字. 代码 1.attrs.xml文件 <?xml version="1.0" encoding="utf-8"?> <res

分析Android中View的工作流程

在分析View的工作流程时,需要先分析一个很重要的类,MeasureSpec.这个类在View的测量(Measure)过程中会用到. MeasureSpec MeasureSpec是View的静态内部类,可以理解为是一种测量规格,是一个32位int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而SpecSize是指在某种测量模式下的规格大小.SpecMode有三种模式,分别为: UNSPECIFIED:父容器不对View做限制 EXACTLY:父容器

Android视图绘制流程完全解析,带你一步步深入了解View(二)

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/16330267 在上一篇文章中,我带着大家一起剖析了一下LayoutInflater的工作原理,可以算是对View进行深入了解的第一步吧.那么本篇文章中,我们将继续对View进行深入探究,看一看它的绘制流程到底是什么样的.如果你还没有看过我的上一篇文章,可以先去阅读 Android LayoutInflater原理分析,带你一步步深入了解View(一) . 相 信每个Android