Android开发系列:TextView绘制(四)过程分析

整体流程

onMeasure -> onLayout -> onDraw

onMeasure

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    ...
    if (widthMode == MeasureSpec.EXACTLY) {
        // 精确模式,以父容器给的宽度作当前TextView的宽度.
        width = widthSize;
    } else {
        if (mLayout != null && mEllipsize == null) {
            //调用desired方法计算期望值,如果行数大于1就返回-1,否则返回单行宽度
            des = desired(mLayout);
        }
        //小于0,即行数大于1行,就去判断是否是boring
        if (des < 0) {
            boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
            if (boring != null) {
               mBoring = boring;
            }
        } else {
            fromexisting = true;
        }
        ...
        //加上Drawable的宽度
        final Drawables dr = mDrawables;
        if (dr != null) {
            width = Math.max(width, dr.mDrawableWidthTop);
            width = Math.max(width, dr.mDrawableWidthBottom);
        }
        //计算hint的宽度
        if (mHint != null) {
            ...
        }
        //加上padding边距
        width += getCompoundPaddingLeft() + getCompoundPaddingRight();
        ...
        //最后根据上面计算得到的size-padding的值就是我们单行text实际可以展示的大小
        int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
        if (mLayout == null) {
           makeNewLayout(want, hintWant, boring, hintBoring,
                   width - getCompoundPaddingLeft() - getCompoundPaddingRight(),false);
        }else {
            ...
        }
        //接下来高度计算,和上面的宽度计算类似
        if (heightMode == MeasureSpec.EXACTLY) {
            // Parent has told us how big to be. So be it.
            height = heightSize;
            mDesiredHeightAtMeasure = -1;
        } else {
            //如果设置了Drawable的话,比较2个值得大小,取大的
            //如果设置了maxLines或者maxHeight计算下当前高度有没超过最大高度,超过的话取最大高度
            //如果设置了minLines或者minHeight的话,比较下当前高度和最小高度,取小的
            int desired = getDesiredHeight();
            ...
        }
        ...
    }
}

1、MeasureSpec是一个specMode和specSize信息的32位int值。其中高两位为测量模式specMode,低30位为测量值specSize。测量模式有UNSPECIFIED、EXACTLY、AT_MOST三种。一个View的测量模式由父容器和其自身的LayoutParams共同决定。

一般来说,View测量模式对应的情况如下

EXACTLY

  • View本身设置为固定值,例如20dp/px
  • 父容器为EXACTLY,View本身为match_paret

AT_MOST

  • 父容器为AT_MOST,View本身为wrap_content或match_parent
  • 父容器为EXACTLY,View本身为wrap_content

UNSPECIFIED

  • 父容器为UNSPECIFIED,View本身为wrap_content或match_parent

2、makeNewLayout为布局的创建入口,在这个方法中,主要进行跑马灯、省略号、HintLayout的处理。核心方法还是调用makeSingleLayout。通过它,进行判断,选择创建BoringLayout、StaticLayout、DynamicLayout中的一种。具体创建过程可以看前面的文章。

onLayout

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    if (mDeferScroll >= 0) {
        int curs = mDeferScroll;
        mDeferScroll = -1;
        bringPointIntoView(Math.min(curs, mText.length()));
    }
    // Call auto-size after the width and height have been calculated.
    autoSizeText();
}

layout过程,执行方法bringPointIntoView,根据前面onMeasure确定的各种LayoutParam参数,进行计算确定在布局中的位置。autoSizeText会在设置了自动调节字体大小(mNeedsAutoSizeText为true)时,自动计算和调节字体大小。

onDraw

TextView#onDraw方法中会有各种获取行数据信息的方法,它们就会访问的前面组织好的数据。

@Override
protected void onDraw(Canvas canvas) {
    final int compoundPaddingLeft = getCompoundPaddingLeft();
    final int compoundPaddingTop = getCompoundPaddingTop();
    final int compoundPaddingRight = getCompoundPaddingRight();
    final int compoundPaddingBottom = getCompoundPaddingBottom();
    ...
    if (mEditor != null) {
       mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
    } else {
       layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
    }
    ...
}

绘制之前,取出各个padding,确定绘制区域的范围和坐标。

若mEditor不为null,走Editor#onDraw方法,否则调用Layout#draw。跟踪Editor代码,可以知道onDraw还是会调用Layout#draw或者直接在硬件加速状态下执行drawBackgrounddrawText方法。

Layoutdraw方法会执行drawBackgrounddrawText方法,前者绘制背景,后者调用TextLine对象,进行逐行绘制。

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

public void drawText(Canvas canvas, int firstLine, int lastLine) {
	...
    //遍历,一行行绘制
    for (int lineNum = firstLine; lineNum <= lastLine; lineNum++) {
       ...
       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 {
           		//设置该行的各种参数,包括起始位置、结束位置、span的个数和位置
                tl.set(paint, buf, start, end, dir, directions, hasTab, tabStops,
                        getEllipsisStart(lineNum),
                        getEllipsisStart(lineNum) + getEllipsisCount(lineNum));
                if (justify) {
                    tl.justify(right - left - indentWidth);
                }
                tl.draw(canvas, x, ltop, lbaseline, lbottom);
        }
   	}
}

TextLine#draw

void draw(Canvas c, float x, int top, int y, int bottom) {
	...
    h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
                            runIndex != (runCount - 1) || j != mLen);
    ...
}

private float drawRun(Canvas c, int start,
            int limit, boolean runIsRtl, float x, int top, int y, int bottom,
            boolean needWidth) {
    if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
            float w = -measureRun(start, limit, limit, runIsRtl, null);
            handleRun(start, limit, limit, runIsRtl, c, x + w, top,
                    y, bottom, null, false);
            return w;
        }

     return handleRun(start, limit, limit, runIsRtl, c, x, top,
                y, bottom, null, needWidth);
}

drawRun方法会调用handleRun,这里面会获取你设置的Span的样式,比如是否显示下划线、链接文字颜色等。最后会通过TextLinedrawTextRun方法绘制,显示最终的效果。

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KWMax

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值