Android之TextView的Span样式源代码剖析

【Android之TextView的Span样式源代码剖析】贵有恒,何必三更起、五更眠、最无益,只怕一日曝、十日寒。这篇文章主要讲述Android之TextView的Span样式源代码剖析相关的知识,希望能为你提供帮助。
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。看源代码:

      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的时候。就能够去选择性的去继承类了。


我的博客园地址:http://www.cnblogs.com/flyme2012/





    推荐阅读