Android实现超出固定行数折叠文字“查看全文“、“收起全文“

先上效果图

分析问题

网上有很多关于这个的代码,实现都过于复杂了,github上甚至还看到一篇文章600多行代码,结果一跑起来全是bug。还是自己写吧!!!

如果我们需要换行的"查看全文"、"收起全文"效果那没什么号说的,因为可以直接用两个TextView然后通过判断超过行数还是没有超过行数来判断显示还是隐藏即可。这没有什么难度,这里我们需要实现同一TextView实现。

要在 TextView 的部分文字上添加颜色和点击事件,您可以使用 SpannableStringClickableSpan 来实现。

为了避免重复代码,那我们肯定是自定义View实现,新建一个ScalingTextView,继承AppCompatTextView

java 复制代码
class ScalingTextView(context: Context, attrs: AttributeSet?) :
    AppCompatTextView(context, attrs) {

}

然后我们需要几个参数

xml 复制代码
<!--一段测试文字-->
<string name="scaling_str">这是一段ScalingTextView的折叠测试文字,测试多行显示的时候是否可以"查看全文"、"收起全文"这个功能是否正常呢,但是这个问题必须要超过两行才行,因此我现在每打的一个字都是在凑字数,你懂了吧!!!</string>

SpannableStringClickableSpan 对象

kotlin 复制代码
    private var spannableString: SpannableString? = null

    // 创建 ClickableSpan 对象
    val clickableSpan = object : ClickableSpan() {
        override fun onClick(widget: View) {
            // 在这里处理点击事件
            toggleText()
        }

        override fun updateDrawState(ds: TextPaint) {
            // 设置点击文字的颜色
            ds.color = Color.BLUE
            // 如果不希望点击文字有下划线,可以注释下面这行代码
            ds.isUnderlineText = true
        }
    }
kotlin 复制代码
    fun toggleText() {
        if (isCollapsed) {
            // 展开文本
            maxLines = Integer.MAX_VALUE
            isCollapsed = false
        } else {
            // 折叠文本
            maxLines = maxLinesCollapsed
            isCollapsed = true
        }
    }

当然还有些其他便于设置的参数,例如:

kotlin 复制代码
    private var maxLinesCollapsed: Int = 2//默认折叠行数
    private var isCollapsed: Boolean = false
    private var mOriginText: String //文本内容
    private @ColorInt var mOriginTextColor: Int//折叠文字颜色
    private val DEFAULT_OPEN_SUFFIX = "查看全文"
    private val DEFAULT_CLOSE_SUFFIX = "收起全文"
    private val ellipsis = "..."

当然这些参数我们需要通过xml里直接配置,不用每次都set一堆方法对吧,所以添加自定义属性

xml 复制代码
    <declare-styleable name="scaling_text_view">
        <attr name="content_text" format="string"></attr>
        <attr name="content_text_color" format="color"></attr>
    </declare-styleable>

然后获取这几个自定义参数,大家可以自行增加,这里只为演示内容

kotlin 复制代码
    init {
        val typedValue = context.obtainStyledAttributes(attrs, R.styleable.scaling_text_view)
        mOriginText = typedValue.getString(R.styleable.scaling_text_view_content_text).toString()
        mOriginTextColor = typedValue.getColor(R.styleable.scaling_text_view_content_text_color,ContextCompat.getColor(context,R.color.themeColor)).toInt()
    }

最后我们如何实现功能呢?我们可以从几个方向去分析:

  • 在文字结尾追加上"..."省略号和 "查看全文""收起全文",这个不难
  • 当超出最大限制行数的时候我们需要截取掉多余内容,并且为"..."省略号和 "查看全文""收起全文"空出位置
  • "查看全文""收起全文"添加颜色
  • 最后为 "查看全文""收起全文"添加点击事件
  • 最后的最后刷新文本内容

那么我们可以重写onMeasure

这里我们用到一个方法getLineEnd

kotlin 复制代码
            val lineEndIndex = layout.getLineEnd(maxLinesCollapsed - 1)
            val newText = text.subSequence(
                0, lineEndIndex - ellipsis.length + 1 - DEFAULT_OPEN_SUFFIX.length + 1
            ).toString().trim { it <= ' ' } + ellipsis + DEFAULT_OPEN_SUFFIX

创建SpannableString对象

kotlin 复制代码
spannableString = SpannableString(newText);
kotlin 复制代码
 //设置点击事件
            spannableString?.setSpan(
                clickableSpan,
                newText.lastIndexOf(DEFAULT_OPEN_SUFFIX),
                newText.lastIndexOf(DEFAULT_OPEN_SUFFIX) + DEFAULT_OPEN_SUFFIX.length,
                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
            )
            //设置文本颜色
            spannableString?.setSpan(
                ForegroundColorSpan(mOriginTextColor),
                newText.lastIndexOf(DEFAULT_OPEN_SUFFIX),
                newText.lastIndexOf(DEFAULT_OPEN_SUFFIX) + DEFAULT_OPEN_SUFFIX.length,
                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
            )

最后设置刷新文本

kotlin 复制代码
            text = spannableString
            movementMethod = LinkMovementMethod.getInstance()
            super.onMeasure(widthMeasureSpec, heightMeasureSpec)

好了,我们搞定了,完整代码

kotlin 复制代码
class ScalingTextView(context: Context, attrs: AttributeSet?) :
    AppCompatTextView(context, attrs) {
    private var maxLinesCollapsed: Int = 2
    private var isCollapsed: Boolean = false
    private val TAG: String = ScalingTextView::class.java.simpleName
    private var mOriginText: String
    private @ColorInt var mOriginTextColor: Int

    private val DEFAULT_OPEN_SUFFIX = "查看全文"
    private val DEFAULT_CLOSE_SUFFIX = "收起全文"
    private val ellipsis = "..."
    private var spannableString: SpannableString? = null

    init {
        val typedValue = context.obtainStyledAttributes(attrs, R.styleable.scaling_text_view)
        mOriginText = typedValue.getString(R.styleable.scaling_text_view_content_text).toString()
        mOriginTextColor = typedValue.getColor(R.styleable.scaling_text_view_content_text_color,ContextCompat.getColor(context,R.color.themeColor)).toInt()
		text = mOriginText
    }

    // 创建 ClickableSpan 对象
    val clickableSpan = object : ClickableSpan() {
        override fun onClick(widget: View) {
            // 在这里处理点击事件
            toggleText()
        }

        override fun updateDrawState(ds: TextPaint) {
            // 设置点击文字的颜色
            ds.color = Color.BLUE
            // 如果不希望点击文字有下划线,可以注释下面这行代码
            ds.isUnderlineText = true
        }
    }

    fun toggleText() {
        if (isCollapsed) {
            // 展开文本
            maxLines = Integer.MAX_VALUE
            isCollapsed = false
        } else {
            // 折叠文本
            maxLines = maxLinesCollapsed
            isCollapsed = true
        }
    }


    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        if (layout.lineCount <= maxLinesCollapsed && spannableString == null) {
            //原文本等于或者小于默认折叠行数的时候不追加点击事件等
            return
        }
        if (layout != null && layout.lineCount > maxLinesCollapsed && isCollapsed) {
            val lineEndIndex = layout.getLineEnd(maxLinesCollapsed - 1)
            val newText = text.subSequence(
                0, lineEndIndex - ellipsis.length + 1 - DEFAULT_OPEN_SUFFIX.length + 1
            ).toString().trim { it <= ' ' } + ellipsis + DEFAULT_OPEN_SUFFIX
            spannableString = SpannableString(newText);
            //设置点击事件
            spannableString?.setSpan(
                clickableSpan,
                newText.lastIndexOf(DEFAULT_OPEN_SUFFIX),
                newText.lastIndexOf(DEFAULT_OPEN_SUFFIX) + DEFAULT_OPEN_SUFFIX.length,
                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
            )
            //设置文本颜色
            spannableString?.setSpan(
                ForegroundColorSpan(mOriginTextColor),
                newText.lastIndexOf(DEFAULT_OPEN_SUFFIX),
                newText.lastIndexOf(DEFAULT_OPEN_SUFFIX) + DEFAULT_OPEN_SUFFIX.length,
                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
            )
            text = spannableString
            movementMethod = LinkMovementMethod.getInstance()
            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        }else if (layout != null && !isCollapsed) {
            val newText = mOriginText + DEFAULT_CLOSE_SUFFIX
            spannableString = SpannableString(newText);
            //设置点击事件
            spannableString?.setSpan(
                clickableSpan,
                newText.lastIndexOf(DEFAULT_CLOSE_SUFFIX),
                newText.lastIndexOf(DEFAULT_CLOSE_SUFFIX) + DEFAULT_CLOSE_SUFFIX.length,
                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
            )
            //设置文本颜色
            spannableString?.setSpan(
                ForegroundColorSpan(mOriginTextColor),
                newText.lastIndexOf(DEFAULT_CLOSE_SUFFIX),
                newText.lastIndexOf(DEFAULT_CLOSE_SUFFIX) + DEFAULT_CLOSE_SUFFIX.length,
                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
            )
            text = spannableString
            movementMethod = LinkMovementMethod.getInstance()
            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        }

    }

}

xml里使用,这里默认是展开的,你们默认隐藏的话自己实现

xml 复制代码
        <com.github.demo.wight.ScalingTextView
            android:id="@+id/scalingTextView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="12dp"
            app:content_text="@string/scaling_str"
            app:content_text_color="@color/themeColor"" />
相关推荐
sunnyday04261 小时前
Spring Boot 项目中使用 Dynamic Datasource 实现多数据源管理
android·spring boot·后端
幽络源小助理2 小时前
下载安装AndroidStudio配置Gradle运行第一个kotlin程序
android·开发语言·kotlin
inBuilder低代码平台3 小时前
浅谈安卓Webview从初级到高级应用
android·java·webview
豌豆学姐3 小时前
Sora2 短剧视频创作中如何保持人物一致性?角色创建接口教程
android·java·aigc·php·音视频·uniapp
白熊小北极3 小时前
Android Jetpack Compose折叠屏感知与适配
android
HelloBan3 小时前
setHintTextColor不生效
android
洞窝技术5 小时前
从0到30+:智能家居配网协议融合的实战与思考
android
QING6186 小时前
SupervisorJob子协程异常处理机制 —— 新手指南
android·kotlin·android jetpack
毕设源码-朱学姐6 小时前
【开题答辩全过程】以 基于安卓的停车位管理系统与设计为例,包含答辩的问题和答案
android
PWRJOY7 小时前
解决Flutter构建安卓项目卡在Flutter: Running Gradle task ‘assembleDebug‘...:替换国内 Maven 镜像
android·flutter·maven