大家都知道Android在设计中可以实现最简单的图文混排,也就是通过在文本中添加各种各样的Span来实现一些效果,现在就来对TextView中如何将这些span转化为画布上的特色元素的代码进行简要的分析。
FrameWork中与这个操作有关系的主要是:TextView,DynamicLayout,Editor,Layout,TextLine,ParagraphStyle,CharacterStyle,ReplacementSpan等。
一切的变化缘起于TextView的onDraw,下面就从它开始走起:
看到最后其实是调用了Editor的onDraw来进行的实际工作,代码如下:实际上在方法assumeLayout中已经对layout进行了处理进行了不同的实例化,但是主线中使用的是父类的方法就不做分析了。
最后的判断了一下是否执行硬件加速,硬件加速的绘制流程另文分析这里不做讲解,接下来看layout的draw方法:
很清晰,绘制背景和绘制文本内容,继续
看到这里才发现,实际的工作都是这个TextLine在完成的,set方法将paint,buf等传入,draw方法绘制
注意一下mPaint,mText和mSpanned这三个对象,下面的变换操作都依赖它们。
下面着重分析一下这个方法,绘制文本和文本中的一些文字风格字体颜色等等主要是通过handleText来完成的,主要变换的东西就是wp这个,它包含了之前传入的mPaint,如果处理的span类型是CharacterStyle,就执行span的updateDrawState方法,来修改绘制的mPaint的属性,然后调用handleText绘制文字(比如AbsoluteSizeSpan中修改的就是TextSize)。其他的如果是ReplacementSpan类型的,调用handleReplacement来进行replace处理,最终将调用到replacement.draw()来实现绘制replace元素。比如imageSpan中就是绘制其中的mDrawable。
噢噢,忘记前面还有关于ParagraphStyle的处理了,相信大家在看源码时会看到的,看看就清楚了,我就等有空再讲其补上了。。。
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的处理了,相信大家在看源码时会看到的,看看就清楚了,我就等有空再讲其补上了。。。