ITmob-Ly
发布于 2024-04-18 / 165 阅读
0

Jetpack Compose 实现展开折叠显示更多文本的动画效果

显示大段文字时有时并不需要默认显示全部内容,有时需要将大段文字折叠起来,当用户点击 “显示更多” 按钮时再展开显示全部内容,能给用户带来更好的用户体验。

本文将介绍使用 Jetpack Compose 时怎样实现文本折叠和展开,并通过 SizeAnimationModifier 修饰符让展开/折叠的动画更平滑。

下面介绍常见两种可展开文本的实现方式:

  1. 显示一个按钮给用户来操作展开和折叠操作
  2. 在文本的末尾显示可点击的文字:“Show More” ”Show Less”

通过按钮展开/折叠文本

如下定义了一个可组合项 ExpandableText 通过点击按钮切换展开或折叠文本:

@Composable
fun ExpandableText(
    text: String,
    modifier: Modifier = Modifier,
    collapsedMaxLine: Int = 3,
) {
    var isExpanded by remember { mutableStateOf(false) }
    var expandable by remember { mutableStateOf(false) }

    Column(
        modifier = Modifier.then(modifier),
    ) {
        Text(
            text = text,
            modifier = Modifier
                .fillMaxWidth()
                .animateContentSize(),
            maxLines = if (isExpanded) Int.MAX_VALUE else collapsedMaxLine,
            overflow = TextOverflow.Ellipsis,
            onTextLayout = { textLayoutResult ->
                if (!isExpanded && textLayoutResult.hasVisualOverflow) {
                    expandable = true
                }
            },
        )
        if (expandable) {
            TextButton(
                modifier = Modifier.align(Alignment.End),
                onClick = { isExpanded = !isExpanded },
            ) {
                Text(text = if (isExpanded) "Show Less" else "Show More")
            }
        }
    }
}

上面的可组合项实现了如果文本少于等于 3 行,不显示 “显示更多“ ”显示较少“ 按钮。文本超过三行时显示 “显示更多“ ”显示较少“ 按钮,可以对大段文本进行折叠和展开。

要使用它,只需在需要的地方调用该可组合项即可:

ExpandableText(
    text = "Thank you for taking the time to read this article from itmob.cn . As the author of this blog, I am excited to share my insights, experiences, and stories with all of you.",
)

运行效果如下:

ExpandableText with Button

通过点击文末文字展开/折叠文本

上面的实现是通过点击按钮切换文本折叠或展开的方式很简单,下面介绍实现在文本结尾添加可点击的文字实现展开文本。

实现的两个关键点:

  1. 文本折叠后移除几个字符留给 “Show More” 可点击文字
  2. 通过 AnnotatedString 实现文字的可点击效果
@Composable
fun ExpandableText(
    text: String,
    modifier: Modifier = Modifier,
    collapsedMaxLine: Int = 3,
) {
    var isExpanded by remember { mutableStateOf(false) }
    var expandable by remember { mutableStateOf(false) }
    var lastVisibleCharIndex by remember { mutableIntStateOf(0) }

    val (showMoreText, showLessText) = " ... Show More" to "Show Less"
    val toggleTextStyle = SpanStyle(
        fontWeight = FontWeight.Bold,
        color = MaterialTheme.colorScheme.primary,
    )

    val annotatedText = buildAnnotatedString {
        if (expandable) {
            if (isExpanded) {
                append(text)

                pushStringAnnotation("TAG_TOGGLE", "https://itmob.cn")
                withStyle(style = toggleTextStyle) { append(showLessText) }
                pop()
            } else {
                val textWithToggleSpace = text.substring(startIndex = 0, endIndex = lastVisibleCharIndex)
                    .dropLast(showMoreText.length)
                append(textWithToggleSpace)

                pushStringAnnotation("TAG_TOGGLE", "https://itmob.cn")
                withStyle(style = toggleTextStyle) { append(showMoreText) }
                pop()
            }
        } else {
            append(text)
        }
    }

    Column(
        modifier = Modifier.then(modifier),
    ) {
        ClickableText(
            modifier = Modifier
                .fillMaxWidth()
                .animateContentSize(),
            onClick = { offset ->
                val annotations = annotatedText.getStringAnnotations("TAG_TOGGLE", offset, offset)
                annotations.firstOrNull()?.let {
                    isExpanded = !isExpanded
                }
            },
            text = annotatedText,
            maxLines = if (isExpanded) Int.MAX_VALUE else collapsedMaxLine,
            onTextLayout = { textLayoutResult ->
                if (!isExpanded && textLayoutResult.hasVisualOverflow) {
                    expandable = true
                    lastVisibleCharIndex = textLayoutResult.getLineEnd(collapsedMaxLine - 1)
                }
            },
        )
    }
}

运行效果如下:

ExpandableText with ClickableText

其他

更多关于 AnnotatedString 的介绍参见:

Jetpack Compose 中的文字

Jetpack Compose 中拥有样式的文本

怎样处理 Jetpack Compose 中文本的长按选择和点击事件(如何在 Text 中添加超链接?