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

news2024/11/23 13:47:47

先上效果图

在这里插入图片描述

分析问题

网上有很多关于这个的代码,实现都过于复杂了,github上甚至还看到一篇文章600多行代码,结果一跑起来全是bug。还是自己写吧!!!
如果我们需要换行的"查看全文"、"收起全文"效果那没什么号说的,因为可以直接用两个TextView然后通过判断超过行数还是没有超过行数来判断显示还是隐藏即可。这没有什么难度,这里我们需要实现同一TextView实现。
要在 TextView 的部分文字上添加颜色和点击事件,您可以使用 SpannableStringClickableSpan 来实现。
为了避免重复代码,那我们肯定是自定义View实现,新建一个ScalingTextView,继承AppCompatTextView

class ScalingTextView(context: Context, attrs: AttributeSet?) :
    AppCompatTextView(context, attrs) {

}

然后我们需要几个参数

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

SpannableStringClickableSpan 对象

    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
        }
    }
    fun toggleText() {
        if (isCollapsed) {
            // 展开文本
            maxLines = Integer.MAX_VALUE
            isCollapsed = false
        } else {
            // 折叠文本
            maxLines = maxLinesCollapsed
            isCollapsed = true
        }
    }

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

    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一堆方法对吧,所以添加自定义属性

    <declare-styleable name="scaling_text_view">
        <attr name="content_text" format="string"></attr>
        <attr name="content_text_color" format="color"></attr>
    </declare-styleable>

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

    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

            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 = 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)

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

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里使用,这里默认是展开的,你们默认隐藏的话自己实现

        <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"" />

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/877174.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

用汇编指令求两个数的最大公约数 求for循环实现1~100

1.用汇编指令求两个数的最大公约数 2.用汇编指令求for循环实现1~100

【校招VIP】测试计划之黑盒测试白盒测试

考点介绍&#xff1a; 黑盒测试&白盒测试是大厂和三四线公司校招的必考点。黑盒是以结果说话&#xff0c;白盒往往需要理解实现逻辑。现在商业项目的接口测试往往以白盒为主&#xff0c;也就是需要测试同学自己观察和修改数据库的值进行用例的测试。 但是无论采用哪种测试方…

尚硅谷大数据项目《在线教育之离线数仓》笔记001

视频地址&#xff1a;尚硅谷大数据项目《在线教育之离线数仓》_哔哩哔哩_bilibili 目录 P003 P004【数仓概念讲的颇为详细】 P018 P019 P020 P021 P022 P023 P024 P003 时间切片&#xff1a;时间回溯&#xff0c;找回以前的数据。 P004【数仓概念讲的颇为详细】 核心架…

华为OD机试 - 数据最节约的备份方法 - 二分查找(Java 2023 B卷 100分)

目录 一、题目描述二、输入描述三、输出描述四、解题思路解题思路如下&#xff1a;解题思路分析&#xff1a; 五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 一、题目描述 有若干个文件&#xff0c;使用刻录光盘…

14k字综述视觉大模型

目录 0.导读1.背景介绍1.1基础架构1.2目标函数1.2.1对比式学习1.2.2生成式学习1.3预训练1.3.1预训练数据集1.3.2微调1.3.3提示工程2.基于文本提示的基础模型2.1基于对比学习的方法2.1.1基于通用模型的对比方法2.1.2基于视觉定位基础模型的方法2.2基于生成式的方法2.3基于对比学…

考公-判断推理-逻辑判断

且和或 只能有一个人是我老婆&#xff0c;要么小红&#xff0c;要么小丽&#xff0c;不可能都是我老婆&#xff0c;虽然有些人心里是这么想的 虽然&#xff0c;但是&#xff0c;且 虽然我很丑&#xff0c;但是我很温柔 或的翻译&#xff0c;否一推一 例题 例题 德摩根 例题…

数据可视化工具的三大类报表制作流程分享

电脑&#xff08;pc&#xff09;、移动、大屏三大类型的BI数据可视化报表制作步骤基本相同&#xff0c;差别就在于尺寸调整和具体的报表布局。这对于采用点击、拖拉拽方式来制作报表的奥威BI数据可视化工具来说就显得特别简单。接下来&#xff0c;我们就一起看看不这三大类型的…

全网最全360无死角编写软件测试用例模板【建议收藏】

总体编写策略&#xff1a; 对于测试用例编写来说&#xff0c;常用的四种方法基本就够用了&#xff0c;等价类、边界值、正交实验法、错误推断法&#xff0c;辅以场景测试法、需求/设计转换法、探索式测试思想&#xff0c;可以应付绝大多数产品的测试。个别的产品还需要在某一点…

nestjs 基础、使用 passport 来进行鉴权

回顾一些定义 NestJS 部分 Module 模块结构 模块是一个图状引用关系。 模块的实例化有三种模式。默认情况是 singletones 模式&#xff0c;也就是模块可能被引用&#xff0c;但不同的引用处拿的是同一个共享实例&#xff0c;也就是说一个进程有一个唯一的实例被共享。 模块&a…

动态设备状态监测:智能化生产的关键利器

动态设备状态监测正引领着工业生产的智能化转型。本文将深入探讨动态设备状态监测的意义、PreMaint在其中的角色&#xff0c;以及如何实现智能化生产&#xff0c;提高生产效率和可靠性。 1. 动态设备状态监测的重要性 随着制造业的发展&#xff0c;设备的状态监测变得至关重要…

小程序制作教程:从零开始搭建企业小程序

在如今的数字化时代&#xff0c;企业介绍小程序成为了企业展示与推广的重要工具。通过企业介绍小程序&#xff0c;企业可以向用户展示自己的品牌形象、产品服务以及企业文化等内容&#xff0c;进而提高用户对企业的认知度和信任度。本文将介绍如何从零开始搭建一个企业介绍小程…

基于深度信念网络的西储大学轴承故障分类识别,基于EMD+DBN的西储大学轴承故障识别,LCD+DBN,LMD+DBN

目录 背影 DBN神经网络的原理 DBN神经网络的定义 受限玻尔兹曼机(RBM) (EMD,LCD,LMD)+DBN的深度信念网络的西储大学轴承故障分类识别 基本结构 主要参数 数据 MATALB代码 结果图 展望 背影 DBN是一种深度学习神经网络,拥有提取特征,非监督学习的能力,是一种非常好的分类…

都说go协程性能好,这次我们来试试java协程

java 协程原理 在Java中&#xff0c;协程&#xff08;Coroutine&#xff09;是一种轻量级的线程解决方案&#xff0c;它可以在代码中实现类似于多线程的并发操作&#xff0c;但不涉及线程的创建和切换开销。 在传统的Java多线程编程模型中&#xff0c;线程的切换开销较大&…

18.本地存储

18.1本地存储分类- localStorage 1.作用: 可以将数据永久存储在本地(用户的电脑)&#xff0c;除非手动删除&#xff0c;否则关闭页面也会存在 2.特性: ●可以多窗口(页面)共享(同一浏览器可以共享) ●以键值对的形式存储使用&#xff0c;键值除了数字型都要加引号 3.语法 存…

一站式自动化测试平台-Autotestplat

3.1 自动化平台开发方案 3.1.1 功能需求 3.1.3 开发时间计划 如果是刚入门、但有一点代码基础的测试人员&#xff0c;大概 3 个月能做出演示版(Demo)进行自动化测试&#xff0c;6 个月内胜任开展工作中项目的自动化测试。 如果是有自动化测试基础的测试人员&#xff0c;大概 …

【C# 基础精讲】抽象类与接口

抽象类&#xff08;Abstract Class&#xff09;和接口&#xff08;Interface&#xff09;是面向对象编程中两种重要的概念&#xff0c;它们用于定义类的结构、行为和关系&#xff0c;是实现多态性、代码复用和系统设计的关键手段。在C#及其他面向对象编程语言中&#xff0c;抽象…

一生一芯3——ubuntu下显示器扩展

刚进ubuntu时不知道如何完成屏幕扩展&#xff0c;查阅后发现是显卡驱动问题&#xff0c;这里需要调整内置显示器的驱动 打开附加驱动 选择显卡驱动如上&#xff08;其他没试过&#xff09; 应用更改 -> 下载后重启 重启完成后扩展显示器上就有显示了 在设置中调整显示屏顺…

kriging-contour前端克里金插值

先看效果&#xff1a; 本项目在kriging-contour插件基础上进行了封装&#xff0c;增加了自定义区域插值&#xff0c;gitbub地址。

财报解读:上半年营收净利双增长,珀莱雅已成为真正的国货之光?

夏季炎热&#xff0c;防晒类产品的销量暴涨。根据千牛数据&#xff0c;防晒衣今年5月全网搜索人数同比增长15%&#xff0c;加购人数同比增长29.8%&#xff0c;访问人数同比增加42%。消费者狂热的防晒需求&#xff0c;孕育着巨大的商机&#xff0c;许多企业开始瞄准这一机会。而…

汇编指令练习

1.大小比较&#xff08;循环&#xff09; start: /*mov r0,#0x9mov r1,#0xfb LoopLoop:cmp r0,r1beq stopsubhi r0,r0,r1subcc r1,r1,r0b Loop stop:b stop.end 仿真图 2. 1到100之和 start:mov r0,#0x1mov r1,#0x0b sum sum:add r1,r1,r0add r0,r0,#0x1cmp r0,#0x65beq sto…