小小段间距,竟也费神费力

段间距理论上讲是一个很常见的需求,但是无论是 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 就单独成一段了,因此我们就可以通过控制 \nlineHeight 来模拟端间距。

因为它游离于 ParagraphStyle 外,我们不能单独控制它的 lineHeight, 所以我们可以将整个 TextlineHeight 赋给 \n, 每个段落的 lineHeightParagraphStyle 来控制。

上面的实现可以工作,但是有一个问题,就是段间距要求至少大于字体的高度,如果期望段间距小点,那是不行的。

所以我们需要把 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

全部评论

相关推荐

11-08 10:39
门头沟学院 C++
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务