联系我们
简单又实用的WordPress网站制作教学
当前位置:网站首页 > 程序开发学习 > 正文

Jetpack Compose : 超简单实现文本展开和收起

作者:访客发布时间:2023-12-27分类:程序开发学习浏览:105


导读:前言文本展开和收起功能可以在需要显示较长文本或内容丰富的情况下,提供更好的用户体验和页面可读性,同时减少页面的冗余信息。可以说是发中常见的功能之一。需要实现的点文本超出最大显...

前言

文本展开和收起功能可以在需要显示较长文本或内容丰富的情况下,提供更好的用户体验和页面可读性,同时减少页面的冗余信息。可以说是发中常见的功能之一。

需要实现的点

  1. 文本超出最大显示行数时尾部显示"...展开"
  2. 点击"...展开"时显示全部文本并且尾部显示"...收起"

SVID_20231226_113118_1 00_00_00-00_00_30.gif

看看Text为我们提供了啥

查看Text的源码很容易看到overflow和maxLines两个属性。

@Composable
fun Text(
    // 要显示的文本内容
    text: String,
    // Modifier修饰符
    modifier: Modifier = Modifier,
    // 文本内容溢出处理方式:Clip、Ellipsis、Visible
    overflow: TextOverflow = TextOverflow.Clip,
    // 最大文本行数
    maxLines: Int = Int.MAX_VALUE,
    // 计算新文本布局时执行的回调。
    onTextLayout: (TextLayoutResult) -> Unit = {},
    // 文本的样式配置
    style: TextStyle = LocalTextStyle.current
)

当我们的overflow设置为"Ellipsis"且文本超出最大行数时显示"..."。
因此我们的思路很简单,当文本控件显示"..."时在其尾部盖上"...展开",伪代码如下:

Box {
	Text()
	Text(
		text = "...展开",
		modifier = Modifier
                .graphicsLayer {
                    translationX = ellipsisLeft
                    translationY = ellipsisBottom - size.height
                }
	)
}

如何获得文本内容溢出后的位置

还好Text控件提供了onTextLayout回调返回TextLayoutResult,这里贴对我们有用的几个方法:

class TextLayoutResult constructor(

    val layoutInput: TextLayoutInput,

    val multiParagraph: MultiParagraph,

    val size: IntSize
) {

    val lineCount: Int get() = multiParagraph.lineCount

    fun isLineEllipsized(lineIndex: Int): Boolean = multiParagraph.isLineEllipsized(lineIndex)

    fun getLineTop(lineIndex: Int): Float = multiParagraph.getLineTop(lineIndex)

    fun getLineBottom(lineIndex: Int): Float = multiParagraph.getLineBottom(lineIndex)

    /**
     *  获取指定行右侧X坐标
     */
    fun getLineRight(lineIndex: Int): Float = multiParagraph.getLineRight(lineIndex)

    /**
     *	获取指定位置字符的水平偏移量
     */
    fun getHorizontalPosition(offset: Int, usePrimaryDirection: Boolean): Float =
        multiParagraph.getHorizontalPosition(offset, usePrimaryDirection)

    /**
     *  获取指定偏移量最接近的字符位置。
     */
    fun getOffsetForPosition(position: Offset): Int =
        multiParagraph.getOffsetForPosition(position)

}

ellipsisBottom可以通过getLineBottom直接获取取。

ellipsisLeft就相对要麻烦些,我们先要获取指定行右侧X坐标再减去ellipsisText的width。

先计算下ellipsisText的width,好在compose为我们提供非常便捷的api这里直接贴代码:

val ellipsisMeasure = rememberTextMeasurer()
val ellipsisLayoutResult = ellipsisMeasure.measure(
    text = ellipsisText,
    style = style
)
val ellipsisWidth = ellipsisLayoutResult.size.width

但是直接使用会出现文字部分被覆盖的情况,所以我们通过该值先获取指定偏移量最接近的字符位置,再通过获取指定位置字符的水平偏移量得到真正的值。

val ellipsisLeft = it.getHorizontalPosition(
       it.getOffsetForPosition(
           Offset(
                it.getLineRight(it.lineCount - 1) - ellipsisWidth,
                it.getLineTop(it.lineCount - 1)
           )
       ), true
)

完整代码

@Composable
fun EllipsisText(
    text: AnnotatedString,
    color: Color,
    backgroundColor: Color,
    fontSize: TextUnit = TextUnit.Unspecified,
    lineHeight: TextUnit = TextUnit.Unspecified,
    maxLines: Int = Int.MAX_VALUE,
    inlineContent: Map<String, InlineTextContent> = mapOf(),
    onTextLayout: (TextLayoutResult) -> Unit = {},
    ellipsisText: String = "...全文",
    ellipsisColor: Color = colorResource(R.color.blue),
    onClick: () -> Unit = {},
    onEllipsisClick: () -> Unit = {},
) {
    val style = TextStyle.Default.copy(color = color, fontSize = fontSize)
    var ellipsisBottom by remember { mutableFloatStateOf(0f) }
    var ellipsisLeft by remember { mutableFloatStateOf(0f) }
    val ellipsisMeasure = rememberTextMeasurer()
    val ellipsisLayoutResult = ellipsisMeasure.measure(
        text = ellipsisText,
        style = style
    )
    val ellipsisWidth = ellipsisLayoutResult.size.width
    Box(modifier = Modifier.animateContentSize()) {
        Text(
            text = text,
            modifier = Modifier
                .clickable(
                    onClick = onClick,
                    indication = null,
                    interactionSource = remember { MutableInteractionSource() }
                )
                .background(backgroundColor),
            lineHeight = lineHeight,
            overflow = TextOverflow.Ellipsis,
            maxLines = maxLines,
            inlineContent = inlineContent,
            onTextLayout = {
                val offset = if (maxLines == Int.MAX_VALUE) 0 else ellipsisWidth
                ellipsisBottom = it.getLineBottom(it.lineCount - 1)
                ellipsisLeft = it.getHorizontalPosition(
                    it.getOffsetForPosition(
                        Offset(
                            it.getLineRight(it.lineCount - 1) - offset,
                            it.getLineTop(it.lineCount - 1)
                        )
                    ), true
                )
                if (ellipsisLeft + ellipsisWidth > it.size.width) {
                    ellipsisLeft = it.getHorizontalPosition(
                        it.getOffsetForPosition(
                            Offset(
                                (it.size.width - ellipsisWidth).toFloat(),
                                it.getLineTop(it.lineCount - 1)
                            )
                        ), true
                    )
                }
                onTextLayout(it)
            },
            style = style
        )
        Text(
            text = "$ellipsisText ",
            modifier = Modifier
                .graphicsLayer {
                    translationX = ellipsisLeft
                    translationY = ellipsisBottom - size.height
                }
                .clickable { onEllipsisClick() }
                .background(backgroundColor),
            style = style.copy(color = ellipsisColor)
        )
    }
}

如何使用

var ellipsis by remember { mutableStateOf(false) }
var expand by remember { mutableStateOf(false) }
EllipsisText(
    text = buildAnnotatedString {
        append(
            "I am happy to join with you today in what will go down in history as the greatest demonstration for freedom in the history of our nation."
        )
    },
    color = colorResource(R.color.text_333),
    backgroundColor = colorResource(R.color.background),
    fontSize = 14.sp,
    maxLines = if (expand) Int.MAX_VALUE else 2,
    onTextLayout = {
        ellipsis = it.isLineEllipsized(it.lineCount - 1)
    },
    ellipsisText = if (expand) {
        "...收起"
    } else if (ellipsis) {
        "...展开"
    } else {
        ""
    }
) {
    expand = !expand
}

Thanks

以上就是本篇文章的全部内容,如有问题欢迎指出,我们一起进步。
如果觉得本篇文章对您有帮助的话请点个赞让更多人看到吧,您的鼓励是我前进的动力。
谢谢~~

源代码地址

  • EllipsisText.kt · miaowmiaow/fragmject

推荐阅读

  • Jetpack Compose : 从改造你的登录页面开始 - 掘金 (juejin.cn)
  • Jetpack Compose : 一学就会的自定义下拉刷新&加载更多 - 掘金 (juejin.cn)
  • Jetpack Compose : 优雅的使用WebView - 掘金 (juejin.cn)
  • Jetpack Compose : 一文学会嵌套滚动NestedScrollConnection - 掘金 (juejin.cn)
  • Jetpack Compose : 超简单实现滚轮控件(WheelPicker) - 掘金 (juejin.cn)

标签:文本简单喷气式飞机Compose


程序开发学习排行
最近发表
网站分类
标签列表