小小段间距,竟也费神费力
段间距理论上讲是一个很常见的需求,但是无论是 View
系统的 TextView
, 亦或是 Compose
系统的 Text
,都没有给出一个段间距的设置,也是比较神奇。
使用 Text
组件,如果你想要段间距的效果,那么网上推荐的做法是通过换行符去分割,然后用多个 Text
来承载。
这么做一般情况下是没什么问题的,但是如果你要对 Text
做部分高亮、划线、改变颜色等,就需要用到 Span
这个玩意儿,它是用 Range
区间去控制范围的,如果用多个 Text
的话,那么 Range
的处理就显得很麻烦了。
所以得上黑科技。
那黑科技是什么呢?答案就是 AnnotatedString
可以添加 ParagraphStyle
。 对于文本某个 Range
添加 ParagraphStyle
,那么它就会表现得像加了换行符一样。
@Composable
fun MultiText(modifier: Modifier = Modifier, text: String, lineHeight: TextUnit, paragraphSpace: TextUnit){
val annotationStr = remember(text) {
buildAnnotatedString {
append(text)
var start = 0
var index = text.indexOf("\n")
while (index > 0 && index < text.length - 1) {
addStyle(ParagraphStyle(
lineHeight = lineHeight,
lineHeightStyle = LineHeightStyle(LineHeightStyle.Alignment.Center, LineHeightStyle.Trim.None),
platformStyle = PlatformParagraphStyle(false)
), start, index)
start = index + 1
index = text.indexOf("\n", index + 2)
}
if (start < text.length) {
addStyle(ParagraphStyle(
lineHeight = lineHeight,
lineHeightStyle = LineHeightStyle(LineHeightStyle.Alignment.Center, LineHeightStyle.Trim.None),
platformStyle = PlatformParagraphStyle(false)
), start, text.length)
}
}
}
Text(
modifier = modifier,
text = annotationStr,
lineHeight = paragraphSpace, // Text 的 lineHeigh 实际用于段间距
style = TextStyle(
lineHeightStyle = LineHeightStyle(LineHeightStyle.Alignment.Center, LineHeightStyle.Trim.None),
platformStyle = PlatformTextStyle(false),
)
)
}
在上述代码中,我通过 \n
去分割,为每一段强制补上 ParagraphStyle
, 如此一来,\n
就单独成一段了,因此我们就可以通过控制 \n
的 lineHeight
来模拟端间距。
因为它游离于 ParagraphStyle
外,我们不能单独控制它的 lineHeight
, 所以我们可以将整个 Text
的 lineHeight
赋给 \n
, 每个段落的 lineHeight
由 ParagraphStyle
来控制。
上面的实现可以工作,但是有一个问题,就是段间距要求至少大于字体的高度,如果期望段间距小点,那是不行的。
所以我们需要把 Text
的字号设的特别小,然后真正文字的字号放到每个段里面单独控制。但是因为 ParagraphStyle
无法控制字号,所以我们还得用上 SpanStyle
。
因此代码变更为:
@Composable
fun MultiText(modifier: Modifier = Modifier, text: String, lineHeight: TextUnit, paragraphSpace: TextUnit){
val annotationStr = remember(text) {
buildAnnotatedString {
append(text)
var start = 0
var index = text.indexOf("\n")
while (index > 0 && index < text.length - 1) {
addStyle(ParagraphStyle(...), start, index)
addStyle(SpanStyle(fontSize = 16.sp), start, index) // 用 SpanStyle 控制字号
start = index + 1
index = text.indexOf("\n", index + 2)
}
if (start < text.length) {
addStyle(ParagraphStyle(...), start, text.length)
addStyle(SpanStyle(fontSize = 16.sp), start, text.length)
}
}
}
Text(
modifier = modifier,
text = annotationStr,
lineHeight = paragraphSpace,
fontSize = 1.sp, // Text 本身的字号调整到特别小
style = TextStyle(...)
)
}
经过这一复杂的包装,我们就可以在业务上使用了。
MultiText(
text = text,
lineHeight = 16.sp,
paragraphSpace = 10.sp,
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 14.dp)
)
当然,实际使用场景,可能是是需要对 AnnotatedString
补加划线、高亮等各种功能,而不是抽取这么一个意义不大的组件。只是实现原理就是这样子。
也不知道国外是怎么考量的,这么个常用的需求,为啥就不直接支持下,非得让业务方写这么一大段复杂的代码。
如果是
View
系统想要实现段间距,Span
也能做到,不过不同系统会有坑,所以推荐还是用QMUI
提供的LineTypeView
。
我是古哥E下,前微信读书客户端程序猿 / 自学 5 年中医,维护过上万 Star 开源项目 QMUI Android
,现独立维护好用简洁的 Android
组件库 emo
。
关注我可得:ChatGPT
开发玩法 | 程序员学习经验 | 组件库新变动 | 中医健康调理 。
emo官网:emo.qhplus.cn