Android中的TextView是个显示文字的的UI类。在现实中的需求中,文字有各式各样的样式,TextView本身没有属性去设置实现。我们能够通过Android提供的 SpannableString类封装。Android提供了非常多的Span的类去实现样式,这个样式都是继承自CharacterStyle类。

     在上一篇博客中具体的介绍的怎么使用各种Span类,这篇博客主要是通过看源代码,来分析Span的工作原理。内部的类结构。继承结构。从而达到我们自己能够自己定义一个Span来使用。


        要想剖析Span的原理。我们就须要看懂TextView的大概的绘制流程。一个TextView中的类似是非常复杂的。一点一点看源代码。找顺序。

     首先,在CharcaterStyle类中具有

      public abstract void updateDrawState(TextPaint tp);

既然updateDrawState须要Paint,那么就须要在TextView中的onDraw去调用这种方法,在onDraw方法中传递给画Text的画笔。这种方法才干起作用,那我们顺着看TextView中的onDraw方法,代码太多。我仅仅贴关键代码。

 在TextView的onDraw方法中仅仅有以下的方法调用到了画笔。

Path highlight = getUpdatedHighlightPath();
if (mEditor != null) {
mEditor.onDraw(canvas, layout, highlight, mHighlightPaint,  
            
      cursorOffsetVertical);
else
mHighlightPaint, cursorOffsetVertical);
        }
if (mMarquee != null && mMarquee.shouldDrawGhost()) {
int) mMarquee.getGhostOffset(), 0.0f);
mHighlightPaint, cursorOffsetVertical);
        }

这里能够发现有两个类:Editor和Layout,TextView的onDraw就是在这两个类中去绘制的,继续分别看这两个类的作用。

1.Editor:还没找到出处代码。放下搁置以后再说

DynamicLayout、StaticLayout,这三个类是一些功能的封装,基本的实现还都是在Layout中,

我们看一下Layout中的代码:

public void draw(Canvas canvas, Path highlight, Paint highlightPaint,
int
final long
int
int
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
// 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

int top, int y, int

我们能够直接通过操作canvas去自己绘制,你想要怎么绘制,不就全然的听你的么???

 分类之后。我们就能够了解到以后假设须要自己定义Span的时候。就能够去选择性的去继承类了。