Android之TextView的Span样式源码剖析
16lz
2021-01-23
Android中的TextView是个显示文字的的UI类,在现实中的需求中,文字有各式各样的样式,TextView本身没有属性去设置实现,我们可以通过Android提供的 SpannableString类封装。Android提供了很多的Span的类去实现样式,这个样式都是继承自 CharacterStyle类。 在上一篇博客中详细的介绍的怎么使用各种Span类,这篇博客主要是通过看源码,来分析Span的工作原理,内部的类结构,继承结构,从而达到我们自己可以自定义一个Span来使用。
要想剖析Span的原理,我们就需要看懂TextView的大概的绘制流程,一个TextView中的类似是很复杂的,一点一点看源码,找顺序。
首先,在CharcaterStyle类中具有 public abstract void updateDrawState(TextPaint tp); 方法,TextPaint是画笔,我个人认为TextPaint没啥作用,直接当作Paint去看就行了。既然updateDrawState需要Paint,那么就需要在TextView中的onDraw去调用这个方法,在onDraw方法中传递给画Text的画笔,这个方法才能起作用,那我们顺着看TextView中的onDraw方法,代码太多,我只贴关键代码。 在TextView的onDraw方法中只有下面的方法调用到了画笔。 Path highlight = getUpdatedHighlightPath(); if (mEditor != null) { mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical); } else { layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); } if (mMarquee != null && mMarquee.shouldDrawGhost()) { canvas.translate((int) mMarquee.getGhostOffset(), 0.0f); layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); }
这里可以发现有两个类:Editor和Layout,TextView的onDraw就是在这两个类中去绘制的,继续分别看这两个类的作用。 1.Editor:还没找到出处代码。放下搁置以后再说 2.Layout:可以看到Layout有三个子类,BoringLayout、 DynamicLayout、 StaticLayout,这三个类是一些功能的封装,主要的实现还都是在Layout中, 我们看一下Layout中的代码: 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); } drawBackground 绘制背景
drawText 绘制文字
找到了关键的代码了。接着看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 { tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops); tl.draw(canvas, x, ltop, lbaseline, lbottom); }
可以看到的是有个判断条件的,直接就可以绘制文字的,但是我们还没找到有关Span的代码啊,难道没有,不要着急,还有tl.draw。看源码:
我们可以总结一下TextView绘制流程了。
TextView的onDraw----》Layout的draw----》TextLine的Draw----》CharacterStyle的 updateDrawState(如果设置的有Span样式)
绘制的主要的代码还是在Layout的Draw中和TextLine的Draw中。
从类的继承结构图中我简单的CharacterStyle分为两类:一个是直接继承CharacterStyle的,另一个 ReplacementSpan。 第一种:直接继承CharacterStyle的样式是主要跟Paint相关的,只需要更改画笔中的设置即可达到更改目的的。 第二种:继承 ReplacementSpan的,在 ReplacementSpan中有Draw的方法, public abstract void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint); 我们可以直接通过操作canvas去自己绘制,你想要怎么绘制,不就完全的听你的么??? 分类之后,我们就可以了解到以后如果需要自定义Span的时候,就可以去选择性的去继承类了。
来自为知笔记(Wiz)
要想剖析Span的原理,我们就需要看懂TextView的大概的绘制流程,一个TextView中的类似是很复杂的,一点一点看源码,找顺序。
首先,在CharcaterStyle类中具有 public abstract void updateDrawState(TextPaint tp); 方法,TextPaint是画笔,我个人认为TextPaint没啥作用,直接当作Paint去看就行了。既然updateDrawState需要Paint,那么就需要在TextView中的onDraw去调用这个方法,在onDraw方法中传递给画Text的画笔,这个方法才能起作用,那我们顺着看TextView中的onDraw方法,代码太多,我只贴关键代码。 在TextView的onDraw方法中只有下面的方法调用到了画笔。 Path highlight = getUpdatedHighlightPath(); if (mEditor != null) { mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical); } else { layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); } if (mMarquee != null && mMarquee.shouldDrawGhost()) { canvas.translate((int) mMarquee.getGhostOffset(), 0.0f); layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); }
这里可以发现有两个类:Editor和Layout,TextView的onDraw就是在这两个类中去绘制的,继续分别看这两个类的作用。 1.Editor:还没找到出处代码。放下搁置以后再说 2.Layout:可以看到Layout有三个子类,BoringLayout、 DynamicLayout、 StaticLayout,这三个类是一些功能的封装,主要的实现还都是在Layout中, 我们看一下Layout中的代码: 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); } drawBackground 绘制背景
drawText 绘制文字
找到了关键的代码了。接着看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 { tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops); tl.draw(canvas, x, ltop, lbaseline, lbottom); }
可以看到的是有个判断条件的,直接就可以绘制文字的,但是我们还没找到有关Span的代码啊,难道没有,不要着急,还有tl.draw。看源码:
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); } } Ok ,终于找到了Span的出处了。
我们可以总结一下TextView绘制流程了。
TextView的onDraw----》Layout的draw----》TextLine的Draw----》CharacterStyle的 updateDrawState(如果设置的有Span样式)
绘制的主要的代码还是在Layout的Draw中和TextLine的Draw中。
从类的继承结构图中我简单的CharacterStyle分为两类:一个是直接继承CharacterStyle的,另一个 ReplacementSpan。 第一种:直接继承CharacterStyle的样式是主要跟Paint相关的,只需要更改画笔中的设置即可达到更改目的的。 第二种:继承 ReplacementSpan的,在 ReplacementSpan中有Draw的方法, public abstract void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint); 我们可以直接通过操作canvas去自己绘制,你想要怎么绘制,不就完全的听你的么??? 分类之后,我们就可以了解到以后如果需要自定义Span的时候,就可以去选择性的去继承类了。
来自为知笔记(Wiz)
转载于:https://www.cnblogs.com/flyme2012/p/4604810.html
更多相关文章
- 【Android】利用Java代码布局,按钮添加点击事件
- Android webview和js互相调用实现方法
- Android OpenGL ES(六)----进入三维在代码中创建投影矩阵和旋转
- 深入解析android log的分析方法(1)
- Android增量升级的方法和原理
- android ListView常见问题解决方法(滚动背景变黑,去除滑动时阴影,拖
- SPDY协议的Android实现 OKHTTP代码分析