textview - SpannableString 文本的花式玩法

做 android 的 SpannableString 應該都知道,我想大家基本都用過,但是系統研究過的恐怕就不多了,一般大家都是用到時 baidu,google 一下的,哈哈,我也是這樣的啦,這不占著今天有時間好好看一下,從各個方面再次體會一下優秀的 java API 用著是多么讓人舒心,也是鞭策一下自己和優秀還有多少距離。

開頭


SpannableString 從語義上是文本容器的意思,從效果看我們用 SpannableString 包裹指定文字之后可以是實現很多效果、樣式,倒是沒白搭起了這個名字,從這點也能再次體會到見名知意的境界

textview 可以接受 SpannableString 類型的文字,那么我們可以把 SpannableString 理解為字符串 String 的擴展類型

        var span10 = SpannableString(tx10.text)
        tx10.setText(span10)

典型的 SpannableString 應用邏輯為:


1. new 一個 SpannableString 對象,并傳入初始文字
var span10 = SpannableString(tx10.text)

2. new 一個我們需要的文本效果對象,設置其中參數,這里是文字顏色效果
var backColorSpan = BackgroundColorSpan(Color.RED)

3. 通過 setSpan 方法把文本效果對象設置給 SpannableString  對象,指定起始結速位置
span2.setSpan(backColorSpan, tx01.text.indexOf("-") + 1, tx02.text.length, SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)

5. 把 SpannableString  賦值給 textview
tx02.setText(span2)

好了,SpannableString 使用過程很好理解,重點是各種文本的 span 效果對用的寫法,和 setSpan() 方法的理解

setSpan() 方法

setSpan() 方法有4個參數很重要

setSpan(Object what, int start, int end, int flags)
  • what - 文本效果
  • start - 文本效果起作用的起始下標
  • end - 結束下表
  • flags - 下標取值范圍,有4個類型:
    • Spanned.SPAN_INCLUSIVE_EXCLUSIVE - 包前不包后
    • Spanned.SPAN_INCLUSIVE_INCLUSIVE - 包前也包后
    • Spanned.SPAN_EXCLUSIVE_EXCLUSIVE - 不包前也不包后
    • Spanned.SPAN_EXCLUSIVE_INCLUSIVE - 不包前但包后

關于 flags 我發現 SPAN_INCLUSIVE_INCLUSIVE 不起作用,和 SPAN_INCLUSIVE_EXCLUSIVE 的效果一樣,都是包前不包后,所以干脆我就都用 SPAN_INCLUSIVE_EXCLUSIVE 得了

SpannableString 樣式詳解


我把樣式都放在一起了,看著清楚些


Snip20180910_4.png

1. 前景色

什么是前景色,說白了改變文字指定位置顏色,對象類型是 ForegroundColorSpan

// 前景色
var span1 = SpannableString(tx01.text)
var forColorSpan = ForegroundColorSpan(Color.BLUE)
span1.setSpan(forColorSpan, tx01.text.indexOf("-") + 1, tx01.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx01.setText(span1)

2. 背景色

注意背景色和前景色的區別,前景色改的是文字顏色,背景色改的是背景顏色,不會改文字顏色的,隊形類型是 BackgroundColorSpan

// 背景色
var span2 = SpannableString(tx02.text)
var backColorSpan = BackgroundColorSpan(Color.RED)
span2.setSpan(backColorSpan, tx01.text.indexOf("-") + 1, tx02.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx02.setText(span2)

這里有個自定義 BackgroundColorSpan 來實現效果的例子

3. 修改文字大小,使用相對數值

什么是相對,就是指定位置的文字是其他文字的幾倍,比如例子中的 1.6F,注意可以改大也可以改小,對象類型是 RelativeSizeSpan

// 相對文字大小
var span3 = SpannableString(tx03.text)
var relaSzieSpan = RelativeSizeSpan(1.6F)
span3.setSpan(relaSzieSpan, tx03.text.indexOf("-") + 1, tx03.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx03.setText(span3)

4. 修改文字大小,使用絕對數值

絕對數值就是我們不再指定文字大小倍數了,而是用具體的數了,比如 26sp,當然我們需要轉換單位,把 sp 轉成 int,對象類型是 AbsoluteSizeSpan

// 絕對文字大小
var span31 = SpannableString(tx031.text)
val value: Int = (this@SpannableStringActivity.resources.displayMetrics.scaledDensity * 26 + 0.5f).toInt()
var absSizeSpan = AbsoluteSizeSpan(value)
span31.setSpan(absSizeSpan, tx031.text.indexOf("-") + 1, tx031.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx031.setText(span31)

5. 刪除線

刪除線也叫中劃線,沒什么特別的,對象類型是 StrikethroughSpan,就是這么名字真特么難記......

// 刪除線
var span4 = SpannableString(tx04.text)
var strSpan = StrikethroughSpan()
span4.setSpan(strSpan, tx04.text.indexOf("-") + 1, tx04.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx04.setText(span4)

6. 下劃線

下劃線是很常見的需求了,對象類型是 UnderlineSpan,這個名字就比中劃線的好記

// 下劃線
var span5 = SpannableString(tx05.text)
var underSpan = UnderlineSpan()
span5.setSpan(underSpan, tx05.text.indexOf("-") + 1, tx05.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx05.setText(span5)

7. 上標

SpannableString 支持設置多個 span ,所以這里相應的縮小了上標的大小,對象類型是 SuperscriptSpan,又是一個不好記的名字,蛋疼......

// 上標
var span6 = SpannableString(tx06.text)
var supSpan = SuperscriptSpan()
span6.setSpan(supSpan, tx06.text.indexOf("-") + 1, tx06.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
var sizeSpan6 = RelativeSizeSpan(0.5f)
span6.setSpan(sizeSpan6, tx06.text.indexOf("-") + 1, tx06.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx06.setText(span6)

8. 上標

這里我遇到問題了,下標顯示不全的問題,我就是縮小了下表相應的大小也沒用,不知道為啥,好在平時下標也用不到,真要是碰到了,我縮小字體大小代替下,對象類型是 SubscriptSpan,繼續吐槽,不說我不舒服斯基,上標下標,這2單詞怎么這么難記

// 下標
var span7 = SpannableString(tx07.text)
var subSpan = SubscriptSpan()
span7.setSpan(subSpan, tx07.text.indexOf("-") + 1, tx07.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx07.setText(span7)

9. 斜體、粗體

這個沒啥說的了,Typeface 里還有一個斜粗體效果,對象類型是 StyleSpan

// 斜體、粗體
var span8 = SpannableString(tx08.text)
var stypeSpan1 = StyleSpan(Typeface.BOLD)
var stypeSpan2 = StyleSpan(Typeface.ITALIC)
span8.setSpan(stypeSpan1, tx08.text.indexOf("-") + 1, tx08.text.indexOf("、"),SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
span8.setSpan(stypeSpan2, tx08.text.indexOf("、") + 1, tx08.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx08.setText(span8)

10. 圖片

圖片這里要重點說下,和我們預想的一樣,圖片不是插入進來的,和 textview 有本質區別。SpannableString 的圖片是替代的指定位置,指定個數的問題,圖片的寬要是超過替代文字的寬度,那么圖片就會遮擋后面文字,所以這里必須注意,例子里面我臨時插入了 4個k,才把圖片顯示全的

還有一點,不論圖片資源是 vector,還是 drawable ,必須顯式的指定圖片大小才行,要不 SpannableString 顯示不出圖片來

// 圖片
var value9 = StringBuilder(tx09.text)
val drawable = resources.getDrawable(R.mipmap.ic_launcher)
drawable.setBounds(0, 0, 80, 80)
var imageSpan = ImageSpan(drawable)

value9.insert(value9.indexOf("-") + 1, "kkkk")
var span9 = SpannableString(value9)
span9.setSpan(imageSpan, value9.indexOf("k"), value9.indexOf("k") + 4,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx09.setText(span9)

對于大圖,我們會面臨圖片居中的問題,ImageSpan 是文字基線對齊的,所以要讓圖片居中我們需要繼承 ImageSpan 自己去繪制圖片實現居中效果,具體看:

android 傳統實現自定義 Emoji 表情的方法:

11. 可點擊區域

可點擊區域默認帶下劃線,使用系統主題顏色。思路是讓文本關聯一個點擊事件對象,注意必須寫 setMovementMethod() 方法,要不不起作用

// 可點擊區域
var span10 = SpannableString(tx10.text)
span10.setSpan(MyClickSpan(), tx10.text.indexOf("-") + 1, tx10.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx10.setMovementMethod(LinkMovementMethod.getInstance())
tx10.setText(span10)

    inner class MyClickSpan : ClickableSpan() {

        override fun updateDrawState(ds: TextPaint) {
            // 不顯示下劃線
            ds.isUnderlineText = false
        }

        override fun onClick(widget: View?) {
            var intent: Intent = Intent(this@SpannableStringActivity, ClickSpanActivity::class.java)
            this@SpannableStringActivity.startActivity(intent)
        }

    }

找到一篇使用正則找電話,改顏色,加撥打電話的例子,挺好的:

12. 超鏈接

看過上面的可點擊區域,這個超鏈接就能理解了吧,注意 http:// 協議必須寫啊,URLSpan 使用的系統自帶的瀏覽器

// 超鏈接
var span11 = SpannableString(tx11.text)
var urlSpan = URLSpan("http://www.sina.com")
span11.setSpan(urlSpan, tx11.text.indexOf("-") + 1, tx11.text.length,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)
tx11.setMovementMethod(LinkMovementMethod.getInstance())
tx11.setText(span11)

13 文字縮進

這個上面的圖沒有,大家看下面的圖。這里用到的是 LeadingMarginSpan.Standard 這個類

SpannableString spannableString = new SpannableString("文字內容");
// 0 第一行縮進像素,30 非第一行縮進像素
LeadingMarginSpan.Standard span= new LeadingMarginSpan.Standard(0, 30);
spannableString.setSpan(span, 0, spannableString.length(),SpannableString.SPAN_INCLUSIVE_INCLUSIVE);
textview.setText(spannableString);

14 顏色選擇器

Textview 上的顏色選擇器我們不僅可以設置背景色的,文字顏色一樣可以設置,這個就是:ColorStateList

4625917-0f99eec051983179.png
<?xml version="1.0" encoding="utf-8"?>
<selector 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    android:enterFadeDuration="300" 
    android:exitFadeDuration="300">

    <item android:state_pressed="true" android:color="#ffffff"/>
    <item android:color="#000000"/>
</selector>
ColorStateList csl = null;
try {
    =XmlResourceParser xrp = getResources().getXml(R.drawable.button_text);
    csl = ColorStateList.createFromXml(getResources(), xrp);
} catch (XmlPullParserException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}
btn.setTextColor(csl);

參考資料:


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。