Android UI——SpannableString詳細解析

Charming本尊發表於2019-03-01

前言

相信很多朋友在日常開發中都遇到過這樣的問題:有一段文字,需要單獨給它各部分文字設定不同的樣式,有的文字設定為粗體,有的文字設定特殊的顏色,有的地方要加入表情,遇到數學公式還可能要設定上下標,這時候該怎麼辦呢?

有的人可能會說:簡單,不同樣式的文字就用不同的TextView,這樣就可以完美解決了。先不說這個方法行不行得通,事實上,若採用這種方式,當碰上一段文字需要設定非常多的樣式時,光是這一堆TextView就夠浪費資源的了,佈局還複雜,也不利於維護,因此這種方式一般不會被採用。

那麼有其他辦法嗎?有,並且還很簡單,今天介紹的這個SpannableString就是用來解決這個問題的。

SpannableString

什麼是SpannableString?

SpannableString,是CharSequence的一種,原本的CharSequence只是一串字元序列,沒有任何樣式,而SpannableString可以在字元序列基礎上對指定的字元進行潤飾,在開發中,TextView可以通過setText(CharSequence)傳入SpannableString作為引數,來達到顯示不同樣式文字的效果。

建立方式

SpannableString spannableString = new SpannableString("如果我是陳奕迅");

複製程式碼

如何對SpannableString進行潤飾?

一般通過以下方式進行設定

spannableString.setSpan(Object what, int start, int end, int flags);
複製程式碼

這裡講解一下幾個引數的意義

  • what:對SpannableString進行潤色的各種Span;
  • int:需要潤色文欄位開始的下標;
  • end:需要潤色文欄位結束的下標;
  • flags:決定開始和結束下標是否包含的標誌位,有四個引數可選
    • SPAN_INCLUSIVE_EXCLUSIVE:包括開始下標,但不包括結束下標
    • SPAN_EXCLUSIVE_INCLUSIVE:不包括開始下標,但包括結束下標
    • SPAN_INCLUSIVE_INCLUSIVE:既包括開始下標,又包括結束下標
    • SPAN_EXCLUSIVE_EXCLUSIVE:不包括開始下標,也不包括結束下標

這裡涉及到一個重要的角色,就是各種各樣的span,它決定我們要對文字的進行怎樣的潤飾,而後三個引數決定潤飾哪些文字,為了方便起見,後面的flags預設都使用SPAN_INCLUSIVE_EXCLUSIVE模式。

各種Span

先來看一張類結構圖,瞭解各種Span之間的關係

Span類結構圖

可以看出所有Span都繼承於CharacterStyle這個抽象類,另外MetricAffectingSpan、ReplacementSpan和ClickableSpan都是抽象類,下面展示一些常用的Span

ForegroundColorSpan

程式碼

SpannableString spannableString = new SpannableString("如果我是陳奕迅");
ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(Color.GREEN);
spannableString.setSpan(foregroundColorSpan, 4, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
mTextView.setText(spannableString);
複製程式碼

ForegroundColorSpan:前景色,也就是對文字上色,顏色設定為GREEN,start為4,end為7,應該是“陳奕迅”三個字顯示為綠色,看一下實際效果

ForegroundColorSpan

BackgroudColorSpan

程式碼

SpannableString spannableString = new SpannableString("如果我是陳奕迅");
BackgroundColorSpan backgroundColorSpan = new BackgroundColorSpan(Color.GREEN);
spannableString.setSpan(backgroundColorSpan, 4, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
mTextView.setText(spannableString);
複製程式碼

BackgroudColorSpan:與ForegroundColorSpan類似,對文字背景上色

BackgroudColorSpan

ClickableSpan

程式碼

SpannableString spannableString = new SpannableString("如果我是陳奕迅");
ClickableSpan clickableSpan = new ClickableSpan() {
    @Override
    public void onClick(View widget) {
        Toast.makeText(MainActivity.this, "如果我是陳奕迅", Toast.LENGTH_SHORT).show();
    }
    @Override
    public void updateDrawState(TextPaint ds) {
        ds.setUnderlineText(false);
    }
};
spannableString.setSpan(clickableSpan, 4, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
mTextView.setMovementMethod(LinkMovementMethod.getInstance());
mTextView.setText(spannableString);
複製程式碼

ClickableSpan:是一個抽象類,實現可點選效果,可以重寫onClick方法實現點選事件,這裡點選“陳奕迅”三個字簡單地彈toast

ClickableSpan

URLSpan

程式碼

SpannableString spannableString = new SpannableString("如果我是陳奕迅");
URLSpan urlSpan = new URLSpan("https://www.baidu.com/s?ie=UTF-8&wd=陳奕迅");
spannableString.setSpan(urlSpan, 4, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
mTextView.setMovementMethod(LinkMovementMethod.getInstance());
mTextView.setText(spannableString);
複製程式碼

URLSpan:實現超連結的效果,繼承於ClickableSpan,點選實現跳轉到瀏覽器

URLSpan

MaskFilterSpan

程式碼

SpannableString spannableString = new SpannableString("如果我是陳奕迅");
MaskFilterSpan embossMaskFilterSpan =
    new MaskFilterSpan(new EmbossMaskFilter(new float[]{10, 10, 10}, 0.5f, 1, 1));
spannableString.setSpan(embossMaskFilterSpan, 0, 4, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
RelativeSizeSpan relativeSizeSpan = new RelativeSizeSpan(1.5f);
spannableString.setSpan(relativeSizeSpan, 0, 4, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
MaskFilterSpan blurMaskFilterSpan = new MaskFilterSpan(new BlurMaskFilter(10, Blur.NORMAL));
spannableString.setSpan(blurMaskFilterSpan, 4, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
mTextView.setText(spannableString);
複製程式碼

MaskFilterSpan:構造方法接受MaskFilter作為引數,其中它有兩個子類:EmbossMaskFilter和BlurMaskFilter
EmbossMaskFilter實現浮雕效果

EmbossMaskFilter(float[] direction, float ambient, float specular, float blurRadius)
複製程式碼
  • direction:float陣列,定義長度為3的陣列標量[x,y,z],來指定光源的方向
  • ambient:環境光亮度,0~1
  • specular:鏡面反射係數
  • blurRadius:模糊半徑,必須>0

BlurMaskFilter實現模糊效果

BlurMaskFilter(float radius, Blur style)
複製程式碼
  • radius:模糊半徑
  • style:有四個引數可選
    • BlurMaskFilter.Blur.NORMAL:內外模糊
    • BlurMaskFilter.Blur.OUTER:外部模糊
    • BlurMaskFilter.Blur.INNER:內部模糊
    • BlurMaskFilter.Blur.SOLID:內部加粗,外部模糊

MaskFilterSpan

RelativeSizeSpan

程式碼

SpannableString spannableString = new SpannableString("如果我是陳奕迅");
RelativeSizeSpan relativeSizeSpan = new RelativeSizeSpan(1.5f);
spannableString.setSpan(relativeSizeSpan, 4, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
mTextView.setText(spannableString);
複製程式碼

RelativeSizeSpan:設定字型的相對大小,這裡設定為TextView大小的1.5倍,看圖

RelativeSizeSpan

AbsoluteSizeSpan

程式碼

SpannableString spannableString = new SpannableString("如果我是陳奕迅");
AbsoluteSizeSpan absoluteSizeSpan = new AbsoluteSizeSpan(40, true);
spannableString.setSpan(absoluteSizeSpan, 4, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
mTextView.setText(spannableString);
複製程式碼

AbsoluteSizeSpan:設定字型的相絕對大小,40表示文字大小,true表示單位為dip,若為false則表示px

AbsoluteSizeSpan

ScaleXSpan

程式碼

SpannableString spannableString = new SpannableString("如果我是陳奕迅");
ScaleXSpan scaleXSpan= new ScaleXSpan(1.5f);
spannableString.setSpan(scaleXSpan, 4, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
mTextView.setText(spannableString);
複製程式碼

ScaleXSpan:設定字型x軸縮放,1.5表示x軸放大為1.5倍,效果如圖

ScaleXSpan

StyleSpan

程式碼

SpannableString spannableString = new SpannableString("如果我是陳奕迅");
StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
StyleSpan italicSpan = new StyleSpan(Typeface.ITALIC);
StyleSpan boldItalicSpan = new StyleSpan(Typeface.BOLD_ITALIC);
spannableString.setSpan(boldSpan, 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
spannableString.setSpan(italicSpan, 2, 4, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
spannableString.setSpan(boldItalicSpan, 4, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
mTextView.setText(spannableString);
複製程式碼

StyleSpan:設定文字樣式,如斜體、粗體

StyleSpan

TypefaceSpan

程式碼

SpannableString spannableString = new SpannableString("如果我是陳奕迅");
TypefaceSpan monospace = new TypefaceSpan("monospace");
TypefaceSpan serif = new TypefaceSpan("serif");
TypefaceSpan sans_serif = new TypefaceSpan("sans-serif");
spannableString.setSpan(monospace, 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
spannableString.setSpan(serif, 2, 4, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
spannableString.setSpan(sans_serif, 4, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
mTextView.setText(spannableString);
複製程式碼

TypefaceSpan:設定文字字型型別,如monospace、serif和sans-serif等等

TypefaceSpan

TextAppearanceSpan

程式碼

SpannableString spannableString = new SpannableString("如果我是陳奕迅");
TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(this, android.R.style.TextAppearance_Material);
spannableString.setSpan(textAppearanceSpan, 4, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
mTextView.setText(spannableString);
複製程式碼

TextAppearanceSpan:設定文字外貌,通過style資源設定,這裡使用系統的style資源

TextAppearanceSpan

UnderlineSpan

程式碼

SpannableString spannableString = new SpannableString("如果我是陳奕迅");
UnderlineSpan underlineSpan = new UnderlineSpan();
spannableString.setSpan(underlineSpan, 4, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
mTextView.setText(spannableString);
複製程式碼

UnderlineSpan:設定文字下劃線,強調突出文字時可以使用該span

UnderlineSpan

StrikethroughSpan

程式碼

SpannableString spannableString = new SpannableString("如果我是陳奕迅");
StrikethroughSpan strikethroughSpan = new StrikethroughSpan();
spannableString.setSpan(strikethroughSpan, 4, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
mTextView.setText(spannableString);
複製程式碼

StrikethroughSpan:設定文字刪除線

StrikethroughSpan

SuperscriptSpan

程式碼

SpannableString spannableString = new SpannableString("如果我是陳奕迅");
SuperscriptSpan superscriptSpan = new SuperscriptSpan();
RelativeSizeSpan relativeSizeSpan = new RelativeSizeSpan(0.8f);
spannableString.setSpan(relativeSizeSpan, 4, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
spannableString.setSpan(superscriptSpan, 4, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
mTextView.setText(spannableString);
複製程式碼

SuperscriptSpan:設定文字為上標

SuperscriptSpan

SubscriptSpan

程式碼

SpannableString spannableString = new SpannableString("如果我是陳奕迅");
SubscriptSpan subscriptSpan = new SubscriptSpan();
RelativeSizeSpan relativeSizeSpan = new RelativeSizeSpan(0.8f);
spannableString.setSpan(relativeSizeSpan, 4, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
spannableString.setSpan(subscriptSpan, 4, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
mTextView.setText(spannableString);
複製程式碼

SubscriptSpan:設定文字為下標

SubscriptSpan

ImageSpan

程式碼

SpannableString spannableString = new SpannableString("如果我是陳奕迅");
ImageSpan imageSpan = new ImageSpan(this, R.drawable.ic_eason);
spannableString.setSpan(imageSpan, 4, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
mTextView.setText(spannableString);
複製程式碼

ImageSpan:設定圖片

ImageSpan

總結

總結一下以上提到的Span

  • ForegroundColorSpan:前景色
  • BackgroundColorSpan:背景色
  • ClickableSpan:抽象類,可點選效果,重寫onClick方法響應點選事件
  • URLSpan:超連結
  • MaskFilterSpan:EmbossMaskFilter浮雕效果,BlurMaskFilter模糊效果
  • RelativeSpan:文字相對大小
  • AbsoluteSpan:文字絕對大小
  • ScaleXSpan:x軸縮放
  • styleSpan:文字樣式
  • TypefaceSpan:文字字型型別
  • TextApearanceSpan:文字外貌
  • UnderlineSpan:下劃線
  • StrikeThroughSpan:刪除線
  • SuperscriptSpan:上標
  • SubscriptSpan:下標
  • ImageSpan:圖片

這些Span能夠很好地幫助我們潤色文字,以非常簡單地方式獲得複雜和絢麗的文字效果,著實是開發中的一大利器,喜歡的朋友收藏備用吧

感謝閱讀!

歡迎關注個人微信公眾號:Charming寫字的地方

相關文章