TextView中设置Span的源码浅析

大家都知道Android在设计中可以实现最简单的图文混排,也就是通过在文本中添加各种各样的Span来实现一些效果,现在就来对TextView中如何将这些span转化为画布上的特色元素的代码进行简要的分析。
FrameWork中与这个操作有关系的主要是:TextView,DynamicLayout,Editor,Layout,TextLine,ParagraphStyle,CharacterStyle,ReplacementSpan等。
一切的变化缘起于TextView的onDraw,下面就从它开始走起:
protected void onDraw(Canvas canvas) {
……
if (mLayout == null) {
            assumeLayout();
        }
……
if (mEditor != null) {
            mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
        } else {
            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
        }
……
}


看到最后其实是调用了Editor的onDraw来进行的实际工作,代码如下:实际上在方法assumeLayout中已经对layout进行了处理进行了不同的实例化,但是主线中使用的是父类的方法就不做分析了。
void onDraw(Canvas canvas, Layout layout, Path highlight, Paint highlightPaint,
            int cursorOffsetVertical) {
……
if (mTextView.canHaveDisplayList() && canvas.isHardwareAccelerated()) {
            drawHardwareAccelerated(canvas, layout, highlight, highlightPaint,
                    cursorOffsetVertical);
        } else {
            layout.draw(canvas, highlight, highlightPaint, cursorOffsetVertical);
        }
}


最后的判断了一下是否执行硬件加速,硬件加速的绘制流程另文分析这里不做讲解,接下来看layout的draw方法:
public void draw(Canvas canvas, Path highlight, Paint highlightPaint,
            int cursorOffsetVertical) {
……
drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical,
                firstLine, lastLine);
        drawText(canvas, firstLine, lastLine);
}


很清晰,绘制背景和绘制文本内容,继续
public void drawText(Canvas canvas, int firstLine, int lastLine) {
……
TextLine tl = TextLine.obtain();
…….
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 {
    tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops);
    tl.draw(canvas, x, ltop, lbaseline, lbottom);
}
……
}


看到这里才发现,实际的工作都是这个TextLine在完成的,set方法将paint,buf等传入,draw方法绘制
void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
            Directions directions, boolean hasTabs, TabStops tabStops) {
        mPaint = paint;
        mText = text;
        ……
        mSpanned = null;

        boolean hasReplacement = false;
        if (text instanceof Spanned) {
            mSpanned = (Spanned) text;
            mReplacementSpanSpanSet.init(mSpanned, start, limit);
            hasReplacement = mReplacementSpanSpanSet.numberOfSpans > 0;
        }
……
}


注意一下mPaint,mText和mSpanned这三个对象,下面的变换操作都依赖它们。
void draw(Canvas c, float x, int top, int y, int bottom) {
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;
            }
        }
……
}

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

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) {
……
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);
}
mMetricAffectingSpanSpanSet.init(mSpanned, mStart + start, mStart + limit);
mCharacterStyleSpanSet.init(mSpanned, mStart + start, mStart + limit);
……

TextPaint wp = mWorkPaint;
            wp.set(mPaint);
ReplacementSpan replacement = null;
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) {
                    replacement = (ReplacementSpan)span;
                } else {
                    // We might have a replacement that uses the draw
                    // state, otherwise measure state would suffice.
                    span.updateDrawState(wp);
                }
            }

            if (replacement != null) {
                x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y,
                        bottom, fmi, needWidth || mlimit < measureLimit);
                continue;
            }
……
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);
                }

                x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
                        top, y, bottom, fmi, needWidth || jnext < measureLimit);
            }
……
}


下面着重分析一下这个方法,绘制文本和文本中的一些文字风格字体颜色等等主要是通过handleText来完成的,主要变换的东西就是wp这个,它包含了之前传入的mPaint,如果处理的span类型是CharacterStyle,就执行span的updateDrawState方法,来修改绘制的mPaint的属性,然后调用handleText绘制文字(比如AbsoluteSizeSpan中修改的就是TextSize)。其他的如果是ReplacementSpan类型的,调用handleReplacement来进行replace处理,最终将调用到replacement.draw()来实现绘制replace元素。比如imageSpan中就是绘制其中的mDrawable。

噢噢,忘记前面还有关于ParagraphStyle的处理了,相信大家在看源码时会看到的,看看就清楚了,我就等有空再讲其补上了。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值