探索 Android 中的 Span
在 Android 中,使用 Span 定義文本的樣式. 通過(guò) Span 改變幾個(gè)文字的顏色,讓它們可點(diǎn)擊笛钝,放縮文字的大小甚至是繪制自定義的項(xiàng)目符號(hào)點(diǎn)(bullet points,國(guó)外人名中名字之間的間隔符號(hào) · 愕宋,HTML 中無(wú)序列表項(xiàng)的默認(rèn)符號(hào))玻靡。Span 能夠改變 TextPaint
屬性,在 Canvas
上繪制中贝,甚至是改變文本的布局和影響像行高這樣的元素囤捻。Span 是可以附加到文本或者從本文分離的標(biāo)記對(duì)象(markup objects);它們可以被應(yīng)用到部分或整段的文本中雄妥。
讓我們來(lái)看看Span如何使用最蕾、提供了哪些開(kāi)箱即用的功能依溯、怎樣簡(jiǎn)單地創(chuàng)建我們自己的 Span 以及如何使用和測(cè)試它們老厌。
Testing custom spans implementation
在 Android 上定義文本樣式
Android 提供了幾種定義文本樣式的方法:
- 單一樣式 —— 樣式應(yīng)用在 TextView 顯示的整個(gè)文本
- 多重樣式 —— 多種樣式應(yīng)用在字符或者段落級(jí)別的文本
單一樣式 使用 XML 屬性或者 樣式和主題 引入了 TextView 的所有內(nèi)容的樣式瘟则。這種方式實(shí)現(xiàn)簡(jiǎn)單,通過(guò) XML 即可實(shí)現(xiàn)枝秤,但是并不能只定義部分內(nèi)容的樣式醋拧。舉個(gè)例子,通過(guò)設(shè)置 textStyle=”bold”
淀弹,所有的文本都會(huì)變?yōu)楹隗w丹壕;你不能只定義特定的幾個(gè)字符為黑體。
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="32sp"
android:textStyle="bold"/>
多重樣式 引入了給一段文本添加多種樣式的功能薇溃。例如菌赖,一個(gè)單詞斜體而另一個(gè)粗體。多重樣式可以通過(guò)使用 HTML 標(biāo)簽沐序、 Span 或者是在 Canvas 上處理自定義的文本繪制琉用。
左圖:?jiǎn)我粯邮轿谋咎亟恪TO(shè)置了 textSize=”32sp” 和 textStyle=”bold” 的 TextView 晶丘。右圖:多重樣式文本。設(shè)置了 ForegroundColorSpan, StyleSpan(ITALIC), ScaleXSpan(1.5f), StrikethroughSpan 的文本唐含。
HTML 標(biāo)簽是解決簡(jiǎn)單問(wèn)題的簡(jiǎn)單辦法浅浮,例如使文本加粗、斜體捷枯,甚至是顯示項(xiàng)目符號(hào)點(diǎn)脑题。為了展示含有 HTML 標(biāo)簽的文本,使用 Html.fromHtml
方法铜靶。在內(nèi)部實(shí)現(xiàn)時(shí)叔遂,HTML 標(biāo)簽被轉(zhuǎn)換成了 span 。但是請(qǐng)注意争剿,Html
類(lèi)并不支持完整的 HTML 標(biāo)簽和 CSS 樣式已艰,例如將小黑點(diǎn)改為其他的顏色。
val text = "My text <ul><li>bullet one</li><li>bullet two</li></ul>"
myTextView.text = Html.fromHtml(text)
當(dāng)你有文本樣式的需求蚕苇,但是 Android 平臺(tái)默認(rèn)不支持時(shí)哩掺,你還可以手動(dòng)地在 Canvas 上繪制文本,例如讓文字彎曲排布涩笤。
Span 允許你實(shí)現(xiàn)具有更細(xì)粒度自定義的多重樣式文本嚼吞。舉個(gè)例子盒件,通過(guò) BulletSpan,你可以定義你的段落文本擁有項(xiàng)目符號(hào)點(diǎn)舱禽。你可以定制文本和點(diǎn)號(hào)之間的間距和點(diǎn)號(hào)的顏色炒刁。從 Android P 開(kāi)始,你甚至可以 設(shè)置點(diǎn)號(hào)的半徑 誊稚。你也可以創(chuàng)建 span 的自定義實(shí)現(xiàn)翔始。在文章中查看 “創(chuàng)建自定義 span” 部分可以找到如何實(shí)現(xiàn)。
val spannable = SpannableString("My text \nbullet one\nbullet two")
spannable.setSpan(
BulletPointSpan(gapWidthPx, accentColor),
/* 起始索引 */ 9, /* 終止索引 */ 18,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
spannable.setSpan(
BulletPointSpan(gapWidthPx, accentColor),
/* 起始索引 */ 20, /* 終止索引 */ spannable.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
myTextView.text = spannable
左圖:使用 HTML 標(biāo)簽;中圖:使用 BulletSpan狼电,默認(rèn)圓點(diǎn)大醒鸦摇;右圖:在 Android P 上使用 BulletSpan 或者自定義實(shí)現(xiàn)漫萄。
你可以組合使用單一樣式和多重樣式卷员。你可以考慮將設(shè)置給 TextView 的樣式作為一種“基本”樣式,而 span 文本樣式是應(yīng)用在基本樣式“之上”并且會(huì)覆蓋基本樣式的樣式腾务。例如毕骡,當(dāng)給一個(gè) TextView 設(shè)置了 textColor=”@color.blue”
屬性且給頭4個(gè)字符應(yīng)用了 ForegroundColorSpan(Color.PINK)
,則頭4個(gè)字符會(huì)使用 span 設(shè)置的粉色岩瘦,而其他文本使用 TextView 屬性設(shè)置的顏色未巫。
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/blue"/>
val spannable = SpannableString(“Text styling”)
spannable.setSpan(
ForegroundColorSpan(Color.PINK),
0, 4,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
myTextView.text = spannable
TextView 組合使用 XML 屬性和 span 樣式
應(yīng)用 span
當(dāng)使用 span 時(shí),你會(huì)和以下類(lèi)的其中之一打交道:SpannedString, SpannableString 或 SpannableStringBuilder启昧。 它們之間的區(qū)別在于文本或標(biāo)記對(duì)象是可改變的還是不可改變的以及它們使用的內(nèi)部結(jié)構(gòu):SpannedString
和 SpannableString
使用線(xiàn)性數(shù)組記錄已添加的 span叙凡,而 SpannableStringBuilder
使用 區(qū)間樹(shù)。
下面是如何決定使用哪一個(gè)的方法:
- 僅 讀取而不設(shè)置 文本和 span密末? -->
SpannedString
-
設(shè)置文本和 span握爷? -->
SpannableStringBuilder
- 設(shè)置 少量的 span (<~ 10)? -->
SpannableString
- 設(shè)置 大量的 span (>~ 10)严里? -->
SpannableStringBuilder
舉個(gè)例子新啼,你用到的文本并不會(huì)改變,但你想要附加 span 時(shí)刹碾,你應(yīng)該使用 SpannableString
燥撞。
║ 類(lèi) ║ 可變文本 ║ 可變標(biāo)記 ║
═════════════════════════════════════════════════════════
║ SpannedString ║ 否 ║ 否 ║
║ SpannableString ║ 否 ║ 是 ║
║ SpannableStringBuilder ║ 是 ║ 是 ║
上面所有的這些類(lèi)都繼承自 Spanned 接口,但擁有可變標(biāo)記的類(lèi)( SpannableString
和 SpannableStringBuilder
) 同時(shí)也繼承自 Spannable。
Spanned --> 帶有不可變標(biāo)記的不可變文本
Spannable (繼承自 Spanned) --> 帶有可變標(biāo)記的不可變文本
通過(guò) Spannable
對(duì)象調(diào)用 setSpan(Object what, int start, int end, int flags)
方法應(yīng)用 span物舒。 What
對(duì)象是一個(gè)標(biāo)記色洞,應(yīng)用于從開(kāi)始到結(jié)束的索引之間的文本。falg 標(biāo)志位標(biāo)記了 span 是否應(yīng)該擴(kuò)展至包含插入文本的開(kāi)始和結(jié)束的點(diǎn)冠胯。任何標(biāo)志位設(shè)置以后火诸,只要插入文本的位置位于開(kāi)始位置和結(jié)束位置之間,span 就會(huì)自動(dòng)的擴(kuò)展涵叮。
舉個(gè)例子惭蹂,設(shè)置 ForegroundColorSpan
可以像下面這樣完成:
val spannable = SpannableStringBuilder(“Text is spantastic!”)
spannable.setSpan(
ForegroundColorSpan(Color.RED),
8, 12,
Spannable.SPAN_EXCLUSIVE_INCLUSIVE)
因?yàn)?span 設(shè)置時(shí)使用了 SPAN_EXCLUSIVE_INCLUSIVE
標(biāo)志位伞插,在 span 的后面插入文本時(shí)割粮,新插入的文本也會(huì)自動(dòng)地繼承此 span。
val spannable = SpannableStringBuilder(“Text is spantastic!”)
spannable.setSpan(
ForegroundColorSpan(Color.RED),
/* start index */ 8, /* end index */ 12,
Spannable.SPAN_EXCLUSIVE_INCLUSIVE)
spannable.insert(12, “(& fon)”)
左圖:帶有 ForegroundColorSpan 的文本;右圖:帶有 ForegroundColorSpan 和 Spannable.SPAN_EXCLUSIVE_INCLUSIVE 的文本耗美。
如果 span 設(shè)置了 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
標(biāo)志位京髓,在 span 后面插入文本時(shí)則不會(huì)修改 span 的結(jié)束索引。
多個(gè) span 可以被組合且同時(shí)附加到同一段文本上商架。例如堰怨,粗體紅色的文本可以像這樣構(gòu)建:
val spannable = SpannableString(“Text is spantastic!”)
spannable.setSpan(
ForegroundColorSpan(Color.RED),
8, 12,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
spannable.setSpan(
StyleSpan(BOLD),
8, spannable.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
帶有多種 span 的文本:ForegroundColorSpan(Color.RED) 和 StyleSpan(BOLD)
Framework span
Android framework 定義了幾個(gè)接口和抽象類(lèi),它們會(huì)在測(cè)量和渲染時(shí)被檢查蛇摸。這些類(lèi)有允許 span 訪(fǎng)問(wèn)像 TextPaint
或 Canvas
對(duì)象的方法备图。
Android framework 在 android.text.style
包、主要接口的字類(lèi)和抽象類(lèi)中提供了20+的 span赶袄, 我們可以通過(guò)下面幾個(gè)方式對(duì) span 進(jìn)行分類(lèi):
- 基于 span 是否只改變外形或者 text 的大小或布局
- 基于 是否會(huì)在字符或段落級(jí)別影響文本
span 類(lèi)別:字符對(duì)比段落,外形對(duì)比大小
影響外形對(duì)比影響尺寸的 span
第一種類(lèi)型以修改外形的方式在字符級(jí)別起作用:文本或背景顏色饿肺、下劃線(xiàn)蒋困、中橫線(xiàn)等等,它會(huì)觸發(fā)文本重新繪制但是并不會(huì)重新布局敬辣。這些 span 引入了 UpdateAppearance
且繼承自 CharacterStyle
. CharacterStyle
字類(lèi)通過(guò)提供更新 TextPaint
的訪(fǎng)問(wèn)方法雪标,定義了怎樣繪制文本。
影響外形的 span
影響尺寸的 span 更改了文本的尺寸和布局溉跃,因此觀(guān)察 span 的變化的對(duì)象會(huì)重新繪制文本以保證布局和渲染的正確村刨。
舉個(gè)例子,影響文本字體大小的 span 要求重新測(cè)量和布局喊积,也要求重新繪制烹困。這種 span 通常繼承自 MetricAffectingSpan
類(lèi)。這個(gè)抽象類(lèi)通過(guò)提供對(duì) TextPaint
的訪(fǎng)問(wèn)乾吻,允許字類(lèi)定義 span 如何影響文本測(cè)量髓梅,而 MetricAffectingSpan
繼承自 CharacterSpan
拟蜻,子類(lèi)在字符級(jí)別影響文本的外形。
影響尺寸的 span
你可能會(huì)想要一直重新創(chuàng)建帶有文本和標(biāo)記的 CharSequence
并且調(diào)用 TextView.setText(CharSequence)
方法枯饿,但是這樣做很有可能一直觸發(fā)已經(jīng)創(chuàng)建好的布局和額外的對(duì)象的重新測(cè)量和重新繪制酝锅。為了減少性能損耗,將文本設(shè)置為 TextView.setText(Spannable, BufferType.SPANNABLE)
奢方,然后當(dāng)你需要更改 span 的時(shí)候搔扁,通過(guò)將 TextView.getText()
轉(zhuǎn)換為 Spannable
從 TextView 獲得 Spannable
對(duì)象。我們會(huì)在未來(lái)的文章中詳細(xì)討論 TextView.setText
的實(shí)現(xiàn)原理和不同的性能優(yōu)化方式蟋字。
舉個(gè)例子稿蹲,考慮通過(guò)這樣的方式設(shè)置和獲取 Spannable
:
val spannableString = SpannableString(“Spantastic text”)
// 將文本設(shè)置為一個(gè) Spannable
textView.setText(spannableString, BufferType.SPANNABLE)
// 然后獲取 TextView 持有的 text 對(duì)象引用
// 這里之所以能轉(zhuǎn)換為 Spannable 是因?yàn)槲覀冎皩⑺O(shè)置為了 BufferType.SPANNABLE
val spannableText = textView.text as Spannable
現(xiàn)在,當(dāng)我們?cè)?spannableText
上設(shè)置了 span 之后鹊奖,我們就不需要再調(diào)用 textView.setText
了苛聘,因?yàn)槲覀冋谥苯有薷?TextView
持有的 CharSequence
對(duì)象的引用。
這是當(dāng)我們?cè)O(shè)置了不同的 span 之后會(huì)發(fā)生什么:
情形1: 影響外觀(guān)的 span
spannableText.setSpan(
ForegroundColorSpan(colorAccent),
0, 4,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
當(dāng)我們附加了一個(gè)影響外觀(guān)的 span 之后忠聚,TextView.onDraw
方法被調(diào)用但 TextView.onLayout
沒(méi)有设哗。這是文本重繪,但寬和高保持原樣两蟀。
情形2: 影響尺寸的 span
spannableText.setSpan(
RelativeSizeSpan(2f),
0, 4,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
因?yàn)?RelativeSizeSpan
改變了文本的大小网梢,文本的寬和高變化,文本的布局方式(舉個(gè)例子赂毯,在 TextView
的大小沒(méi)有變化的情況下战虏,一個(gè)特定的單詞現(xiàn)在可能會(huì)換行)。TextView
需要計(jì)算新的大小所以 onMeasure
和 onLayout
均被調(diào)用欢瞪。
左圖:ForegroundColorSpan——影響外觀(guān)的 span;右圖:RelativeSizeSpan——影響尺寸的 span
影響字符和影響段落的 span
一個(gè) span 對(duì)文本產(chǎn)生的影響既可以在字符級(jí)別遣鼓,更新元素啸盏,如背景顏色、樣式或大小骑祟,也可以在段落級(jí)別回懦,更改整個(gè)文本塊的對(duì)齊或者邊距。根據(jù)所需的樣式次企,span 既可以繼承自CharacterStyle
怯晕,也可以引入 ParagraphStyle
。 繼承自 ParagraphStyle
的 span 必須從第一個(gè)字符附加到單個(gè)段落的最后一個(gè)字符缸棵,否則 span 不會(huì)被顯示舟茶。在 Android 中,段落是基于換行符 (\n) 定義的。
在 Android 中,段落是基于換行符 (\n) 定義的阀捅。
影響段落的 span
舉個(gè)例子胀瞪,像 BackgroundColorSpan
這樣的 CharacterStyle
,可以被附加到文本中的任何字符上饲鄙。這里凄诞,我們把它附加到第五到第八個(gè)字符上。
val spannable = SpannableString(“Text is\nspantastic”)
spannable.setSpan(
BackgroundColorSpan(color),
5, 8,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
ParagraphStyle
的 span忍级,像 QuoteSpan
帆谍,只能夠被附加到段落的開(kāi)始,否則行和文本之間的邊距就不會(huì)出現(xiàn)颤练。例如既忆,“Text is\nspantastic” 在文本的第8個(gè)字符包含了一個(gè)換行符驱负,所以我們可以給它附加一個(gè) QuoteSpan
嗦玖,段落從那里開(kāi)始就會(huì)被添加樣式。如果我們?cè)?或8之外的任何位置附加 span跃脊,text 就不會(huì)被添加樣式宇挫。
spannable.setSpan(
QuoteSpan(color),
8, text.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
左圖:BackgroundColorSpan -- 影響字符的 span酪术。右圖:QuoteSpan -- 影響段落的 span
創(chuàng)建自定義 span
在實(shí)現(xiàn)你自己的 span 時(shí)器瘪,你需要確定你的 span 是否會(huì)影響字符或段落級(jí)別的文本,以及它是否也會(huì)影響文本的布局或外觀(guān)绘雁。但是橡疼,在從頭開(kāi)始編寫(xiě)自己的實(shí)現(xiàn)之前,檢查一下是否可以使用 framework 中提供的 span庐舟。
太長(zhǎng)不看:
- 在 字符級(jí)別 影響文本 ->
CharacterStyle
- 在 段落級(jí)別 影響文本 ->
ParagraphStyle
- 影響 文本外觀(guān) ->
UpdateAppearance
- 影響 文本尺寸 ->
UpdateLayout
假設(shè)我們需要實(shí)現(xiàn)一個(gè) span欣除,它允許以一定的比例增加文本的大小,比如 RelativeSizeSpan
挪略,并設(shè)置文本的顏色历帚,比如 ForegroundColorSpan
。為此杠娱,我們可以擴(kuò)展 RelativeSizeSpan
挽牢,并且 RelativeSizeSpan
提供了 updateDrawState
和 updateMeasureState
回調(diào),我們可以復(fù)寫(xiě)繪制狀態(tài)回調(diào)并設(shè)置 TextPaint
的顏色摊求。
class RelativeSizeColorSpan(
@ColorInt private val color: Int,
size: Float
) : RelativeSizeSpan(size) {
override fun updateDrawState(textPaint: TextPaint?) {
super.updateDrawState(ds)
textPaint?.color = color
}
}
注意:同樣的效果可以通過(guò)在同一文本上同時(shí)應(yīng)用 RelativeSizeSpan
和 ForegroundColorSpan
實(shí)現(xiàn)禽拔。
測(cè)試自定義 span 的實(shí)現(xiàn)
測(cè)試 span 意味著檢查確實(shí)已對(duì) TextPaint 進(jìn)行了預(yù)期的修改,或者是否已經(jīng)將正確的元素繪制到了 canvas 上。例如睹栖,假設(shè)一個(gè) span 的自定義實(shí)現(xiàn)為段落添加制定大小和顏色的項(xiàng)目符號(hào)點(diǎn)寥闪,以及左邊距和項(xiàng)目符號(hào)點(diǎn)之間的間隙。在 android-text sample 查看具體實(shí)現(xiàn)磨淌。為了測(cè)試這個(gè)類(lèi)疲憋,實(shí)現(xiàn)一個(gè) AndroidJUnit 類(lèi),確實(shí)檢查:
- 一個(gè)特定大小的圓被繪制在了 canvas 上
- 如果沒(méi)有被附加到文本上梁只,則沒(méi)有任何繪制
- 基于構(gòu)造函數(shù)的參數(shù)缚柳,設(shè)置了正確的邊距
測(cè)試 Canvas 的交互可以通過(guò) mock canvas,給 drawLeadingMargin
方法傳 mock 過(guò)的引用并使用正確的參數(shù)驗(yàn)證是否已調(diào)用正確的方法來(lái)實(shí)現(xiàn)搪锣。
val canvas = mock(Canvas::class.java)
val paint = mock(Paint::class.java)
val text = SpannableString("text")
@Test fun drawLeadingMargin() {
val x = 10
val dir = 15
val top = 5
val bottom = 7
val color = Color.RED
// 給定一個(gè)已經(jīng)設(shè)置到文本上的 span
val span = BulletPointSpan(GAP_WIDTH, color)
text.setSpan(span, 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
// 當(dāng)前面的邊距已經(jīng)被繪制
span.drawLeadingMargin(canvas, paint, x, dir, top, 0, bottom,
text, 0, 0, true, mock(Layout::class.java))
// 檢查確定的 canvas 和 paint 方法在確定的順序下被調(diào)用
val inOrder = inOrder(canvas, paint)
// bullet point paint color is the one we set
inOrder.verify(paint).color = color
inOrder.verify(paint).style = eq<Paint.Style>(Paint.Style.FILL)
// 一個(gè)確定大小的圓在確定位置被繪制
val xCoordinate = GAP_WIDTH.toFloat() + x.toFloat()
+dir * BulletPointSpan.DEFAULT_BULLET_RADIUS
val yCoord = (top + bottom) / 2f
inOrder.verify(canvas)
.drawCircle(
eq(xCoordinate),
eq(yCoord),
eq(BulletPointSpan.DEFAULT_BULLET_RADIUS),
eq(paint))
verify(canvas, never()).save()
verify(canvas, never()).translate(
eq(xCoordinate),
eq(yCoordinate))
}
在 BulletPointSpanTest
查看其余的測(cè)試秋忙。
測(cè)試 span 的用法
Spanned
接口允許給文本設(shè)置 span 和從文本獲取 span 。通過(guò)實(shí)現(xiàn)一個(gè) Android JUnit 測(cè)試构舟,檢查是否在正確的位置添加了正確的 span灰追。在 android-text sample
我們把項(xiàng)目符號(hào)點(diǎn)標(biāo)記標(biāo)簽轉(zhuǎn)換為了項(xiàng)目符號(hào)點(diǎn)。這是通過(guò)給文本在正確的位置附加 BulletPointSpans
狗超。 下面展示了它是如何被測(cè)試的:
@Test fun textWithBulletPoints() {
val result = builder.markdownToSpans(“Points\n* one\n+ two”)
// 檢查標(biāo)記標(biāo)簽被移除
assertEquals(“Points\none\ntwo”, result.toString())
// 獲取所有附加到 SpannedString 上的 span
val spans = result.getSpans<Any>(0, result.length, Any::class.java)assertEquals(2, spans.size.toLong())
// 檢查 span 確實(shí)是 BulletPointSpan
val bulletSpan = spans[0] as BulletPointSpan
// 檢查開(kāi)始和結(jié)束索引正是期望值
assertEquals(7, result.getSpanStart(bulletSpan).toLong())
assertEquals(11, result.getSpanEnd(bulletSpan).toLong())
val bulletSpan2 = spans[1] as BulletPointSpan
assertEquals(11, result.getSpanStart(bulletSpan2).toLong())
assertEquals(14, result.getSpanEnd(bulletSpan2).toLong())
}
查看 MarkdownBuilderTest
獲取更多測(cè)試示例弹澎。
注意,如果你需要在測(cè)試之外遍歷 span努咐,使用
Spanned#nextSpanTransition
而不是Spanned#getSpans
苦蒿,因?yàn)樗阅芨谩?/p>
span 是一個(gè)非常強(qiáng)大的概念,它深深的嵌入在文本渲染功能中渗稍。它們可以訪(fǎng)問(wèn) TextPaint 和 Canvas 等組件佩迟,這些組件允許在 Android 上使用高度可自定義的文本樣式。在 Android P 中竿屹,我們?yōu)?framework span 添加了大量文檔报强,所以,在實(shí)現(xiàn)你自己的 span 之前拱燃,查看那些能夠獲取到的內(nèi)容秉溉。
在以后的文章中,我們將向你詳細(xì)介紹 span 在底層是如何工作的以及怎樣高效地使用它們扼雏。例如坚嗜,你需要使用 textView.setText(CharSequence, BufferType)
或者 Spannable.Factory
。 有關(guān)原因的詳細(xì)信息诗充,請(qǐng)保持關(guān)注苍蔬。
非常感謝 Siyamed Sinir,Clara Bayarri 和 Nick Butcher.
本文原作者 Florina Muntenescu蝴蜓,Android Developer Advocate @Google . 原文地址:https://medium.com/google-developers/spantastic-text-styling-with-spans-17b0c16b4568. 本文由 TonnyL 翻譯碟绑,發(fā)表在: https://tonnyl.github.io/