Android之TextView的Span樣式源碼剖析,androidtextview
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/