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"" />
相关推荐
_extraordinary_2 分钟前
Linux权限(一)
android·linux·excel
人生!?1 小时前
给小米/红米手机root(工具基本为官方工具)——KernelSU篇
android·linux·智能手机
古苏2 小时前
Android输入事件传递流程系统源码级解析
android
生产队队长3 小时前
ThinkPHP:配置Redis并使用
android·数据库·redis
踏雪羽翼3 小时前
android 差值器的使用
android
Mr-Apple4 小时前
MySQL的Union和OR查询
android·数据库·mysql
yzpyzp4 小时前
如果后台的Long类型的数据返回是null,那么Android客户端的数据bean的kotlin的Long类型的字段接受到数据后是null空指针吗?
android·kotlin
hmywillstronger5 小时前
【Excel】【VBA】根据内容调整打印区域
android·excel
coooliang6 小时前
【Android】ViewPager的使用
android
xvch8 小时前
Kotlin 2.1.0 入门教程(二十五)类型擦除
android·kotlin