前言
前段時間看過一篇 實作類似新浪微網誌文章顯示(2)——話題、@好友、表情解析工具類類似這種富文本的顯示我們一般首先就會想到
SpannableString
,原作者也是這樣實作的,就想着整理一下 相關知識.
SpannableString與SpannableStringBuilder
從名稱來看一個是
builder
, 一個是
string
,很好了解.關于
SpannableString,SpannableStringBuilder及String
的差別可以看這裡
SpannableString與SpannableStringBuilder,
SpannableStringBuilder
可以通過
append()
方法來動态改變其内部的值,與
String
不同的是前兩者都可以通過
span
來添加額外的資訊.
SetSpan方法
方法聲明如下:
void setSpan (Object what, int start, int end, int flags);
參數說明:
- what : 文本格式,可以設定成前景色,背景色,下劃線,中劃線,模糊等
- start : 字元串設定格式的起始下标
- end : 字元串設定格式結束下标
- flags : 辨別
flags: 包含四種情況,用四個常量控制
-
從起始下标到結束下标,包括起始下标不包含結束坐标Spanned.SPAN_INCLUSIVE_EXCLUSIVE
-
從起始下标到結束下标,但都不包括起始下标和結束下标Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
-
從起始下标到終了下标,同時包括起始下标和結束下标Spanned.SPAN_INCLUSIVE_INCLUSIVE
-
從起始下标到終了下标,包括結束下标不包含起始坐标Spanned.SPAN_EXCLUSIVE_INCLUSIVE
Spans架構
關于Android 平台上的
Spans
架構,可以檢視Spans, a Powerful Concept.
,
Spans
主要分為四個層次
- 如果一個Span影響字元級的文本格式,則繼承CharacterStyle.包括我們常用的
.ForegroundColorSpan,BackgroundColorSpan
- 如果一個Span影響段落層次的文本格式,則實作ParagraphStyle.包括了
,繼承自BulletSpan
LeadingMarginSpan
- 如果一個Span修改字元級别的文本外觀,則實作UpdateAppearance.包括了
ClickableSpan
- 如果一個Span修改字元級文本度量|大小,則實作UpdateLayout.包括了
AbsoluteSizeSpan
具體其繼承結構可通過檢視其
Hierarchy
擴充閱讀:
- 上面的中文譯文: Spans,一個強大的概念
- Android Span 架構介紹:介紹了多種
的用法.Spans
工作原理
當你給一個
TextView
設定文本時,它使用Layout去管理文本的渲染。
Layout
包含有三個子類.分别是
-
:負責顯示單行文本,BoringLayout
用于判斷是否是單行文本.isBoring
-
:負責渲染DynamicLayout
,且内部會設定Spannable
,有SpanWatcher
的時候會soan
,進而重新計算布局.reflow
-
: 單行文本,且非StaticLayout
的時候,不會監聽Spannable
變化,效率較span
高.裡面處理了換行.DynamicLayout
-
會負責文本的繪制,其中Layout.draw()
會遞歸drawBackground
并調用LineBackgroundSpan
來進行背景的繪制.lineBackgroundSpan.drawBackground
-
則會首先通過drawText
生成TextLine.obtain()
,如果是文本,則調用TextLine
繪制,如果包含了canvas.drawText
,則交給Spannble,emoji
繪制.TextLine
-
會調用TextLine#draw()
,進而調用drawRun
進行文本的渲染.handleRun()
-
為了提高效率,在4.0之後加入.TextLayoutCache
- 如果想要提升
的渲染效率,可以使用TextView
.StaticLayout
-
擴充閱讀:
- 關于TextView的一些初步講解
- android staticlayout使用講解
- [譯]Instagram是如何提升TextView渲染性能的
- TextView預渲染研究
自定義Span
除了上述四類
Span
,我們還可以自定義
Span
,自定義
Span
如同自定義
View
一樣.可以繼承已有的
Span
(擴充),也可以通過繼承抽象了或者接口完全自定義.
- 擴充已有的Span
類似demo中擴充
ForegroundColorSpan
來實作的
ActionBar
的淡入效果.這裡需要借助屬性動畫的相關知識.
一般需要複寫
updateDrawState
,
getSize
或者
draw
方法.
public void updateDrawState(TextPaint ds){}
updateDrawState
方法最終會被
TextLine#handleRun()
方法調用
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {}
getSize()
方法,傳回新的更換Span後的size
@Override
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
//draw something
canvas.drawText(text, start, end, x, y, paint);
}
draw
使用
Canvas
繪制一些文本之外的東西.可以是
背景
..
- 完全自定義
通過源碼我們發現
CharacterStyle,ParagraphStyle.UpdateAppearance和UpdateLayout
都是一個空接口.我們要實作自定義
Span
,則隻有繼承自其子類,一般是
MetricAffectingSpan,ReplacementSpan,LineBackgroundSpan
比如,如果需要自定義背景則可以繼承
LineBackgroundSpan
,就像Demo中的
LetterLineBackgroundSpan
學習demo:flavienlaurent/spans,裡面列舉了各種 Span
的用法.
FontMetrics
就是所謂的字型規格,如下圖所示
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyM2MDNzADNyITNxETM2EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
FontMetrics
是
Paint
的内部類,裡面包含了一些關于字型的常量.
其中
Baseline(基線)
,
ascent(上坡度)
,
descent(下坡度)
,
leading(行間距)
,這些常量集合
canvas
使得我們的繪制工作變得更加的自由
而我們繪制文字的時候一般使用的是
TextPaint
這個繼承自
Paint
的類.
//擷取文本寬度
TextPaint textPaint = new TextPaint();
paint.setTextSize(size);//設定字型大小
paint.setTypeface(Typeface.xx);//設定字型
float width = Layout.getDesiredWidth(str,textPaint);
圖文混排居中
比如,我們在使用
ImageSpan
的時候,并且使用了
lineSpacingExtra
來設定行間距後,會出現圖檔下沉,即圖檔和文字不再一條線上,這時候就可以通過
FontMetrics
來設定改變.如,評論中插入 圖像,将
span
上移即可
- 解決方法
public class EmojiSpan extends ImageSpan {
public EmojiSpan(Bitmap drawable) {
super(drawable);
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text,
int start, int end, float x,
int top, int y, int bottom, @NonNull Paint paint) {
// image to draw
Drawable b = getDrawable();
// font metrics of text to be replaced
Paint.FontMetricsInt fm = paint.getFontMetricsInt();
int transY = (y + fm.descent + y + fm.ascent) /
- b.getBounds().bottom / ;
canvas.save();
canvas.translate(x, transY);
b.draw(canvas);
canvas.restore();
}
}
擴充閱讀
- Android中測量Text的寬度和高度
- 用TextPaint來繪制文字
- Android自定義View使用canvas繪制文字實作居中、自動換行