Html.fromHtml
老方法也许永不过时:
Html.fromHtml("Text, Bold, italic")
但这个方法也存在不少缺点:拼接变量比较麻烦,虽然使用kotlin的原始字符串可以方便地拼接字符串,但还是有诸如支持的标签比较少,img标签处理比较麻烦,扩展标签和自定义span也很麻烦等问题。
Kotlin
受kotlinx.html的启发,我们可以利用kotlin支持dsl,运算符重载,扩展函数等特性实现下面类似html + css的span写法:
textView.text = createSpan {
style {
h3 {
color = 0xFF8D07F6.toInt()
}
"serif" {
fontFamily = Typeface.SERIF
}
}
h3 { +"::before" }
p {
b { +"KtSpans" }
+" is inspired by "
a("https://github.com/Kotlin/kotlinx.html") {
+"kotlinx.html"
}
}
br {}
h3 { +"#now" }
p {
+"Make spans on android easy to use."
}
br {}
h3 { +"::after" }
p {
className = "serif"
+"More possibilities is comming"
}
}
然后得到这样的结果:
screenshot_01_clipped.png
实现
只是简略的说明主要思路,可以直接跳到下面的Github链接和使用部分。
1. createSpan {}
首先从最外层开始,createSpan 块可以简单地利用带接收者的lambda定义:
inline fun createSpan(body: RootTag.() -> Unit): Spanned {
val root = RootTag()
root.body()
return root.span()
}
2. style {}
style是样式属性块,定义为:
inline fun RootTag.style(body: StyleSheet.() -> Unit) {
this.style = StyleSheet().also(body)
}
StyleSheet类包含了用于存储标签属性的map:
// StyleSheet.kt
val tagAttrs: MutableMap = HashMap()
为了在style块中能定义各个标签的属性,还需要定义这样的函数:
// StyleSheet.kt
inline fun a(body: Attributes.() -> Unit) {
val attrs = tagAttrs[A] ?: Attributes().also {
tagAttrs[A] = it
}
attrs.body()
}
Attributes定义了一些共有的属性:
open class Attributes(
var fontSize: Size? = null,
var fontFamily: Typeface? = null,
var fontStyle: Int = Typeface.NORMAL,
var lineHeight: Size? = null,
var align: Layout.Alignment? = null,
var backgroundColor: Int = 0,
var color: Int = 0,
var decoration: Decoration? = null
)
3. 标签内容的dsl
标签类Tag内部定义了一个SpannableStringBuilder的变量,用于添加文本和子标签:
// Tag.kt
private val spanBuilder = SpannableStringBuilder()
open fun span(): Spanned {
return spanBuilder
}
open fun spanBuilder(): SpannableStringBuilder {
return spanBuilder
}
在Tag中重写String的一元运算符+:
// Tag.kt
operator fun String.unaryPlus() {
spanBuilder.append(this)
}
这样就可以通过+"Text"的方式(kotlinx.html的方式)添加内容了。
最后还需要在定义Tag的扩展函数来实现dsl和span的逻辑:
inline fun Tag.a(url: String, crossinline body: Tag.() -> Unit) {
checkRoot(this)
val tag = Tag(A, root)
tag.body()
root!!.defaultStyle.a {}
insertTag(tag, arrayOf(URLSpan(url)))
}
insertTag()函数包含了样式属性和span等内容的处理,同时把生成好的SpannableStringBuilder添加到父标签中。实现和更多细节可点击下面的Github链接查看项目。
Github和使用
项目地址
基本使用
1. 添加gradle依赖:
implementation 'com.dokar:ktspans:1.0.1'
2. 使用createSpan创建span并使用一些常用标签:
textView.text = createSpan {
// 定义样式
style { ... }
// 标题标签
h1 { ... }
// 水平分割线标签
hr {}
// 段落标签
p { ... }
// 图片标签
img(drawable) { ... }
// 链接标签
a(url) { ... }
// 点击事件标签
clickable(onClick) { ... }
...
}
完整的内置标签:README
3. 标签内属性:
p {
attrs {
fontStyle = Typeface.BOLD
}
+"Some text"
}
完整的共有属性见上面的Atrributes类或浏览README
4. 一些特殊标签的完整属性:
quote {
fullAttrs {
stripeColor = accent
stripeWidth = 4.dp
gapWidth = 8.dp
}
+"A quote here"
}
5. 使用外部的样式:
val appTextStyle = createStyle {
h1 { ... }
p { ... }
quote { ... }
bullet { ... }
hr { ... }
}
然后在 createSpan 中:
createSpan {
style(appTextStyle)
...
}
6. 包含/重用span:
fun header(): Spanned {
val banner = ContextCompat.getDrawable(this, R.mipmap.ic_banner)!!
return createSpans {
img(banner) {}
h1 { +"Title" }
p { ... }
}
}
然后在 createSpan 中:
createSpan {
+header()
p { ... }
...
}
自定义标签: