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

分析问题
网上有很多关于这个的代码,实现都过于复杂了,github上甚至还看到一篇文章600多行代码,结果一跑起来全是bug。还是自己写吧!!!
如果我们需要换行的"查看全文"、"收起全文"效果那没什么号说的,因为可以直接用两个TextView然后通过判断超过行数还是没有超过行数来判断显示还是隐藏即可。这没有什么难度,这里我们需要实现同一TextView实现。
要在 TextView 的部分文字上添加颜色和点击事件,您可以使用 SpannableString 和 ClickableSpan 来实现。
为了避免重复代码,那我们肯定是自定义View实现,新建一个ScalingTextView,继承AppCompatTextView
class ScalingTextView(context: Context, attrs: AttributeSet?) :AppCompatTextView(context, attrs) {}
然后我们需要几个参数
<!--一段测试文字-->
<string name="scaling_str">这是一段ScalingTextView的折叠测试文字,测试多行显示的时候是否可以”查看全文“、”收起全文“这个功能是否正常呢,但是这个问题必须要超过两行才行,因此我现在每打的一个字都是在凑字数,你懂了吧!!!</string>
SpannableString 和 ClickableSpan 对象
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_VALUEisCollapsed = false} else {// 折叠文本maxLines = maxLinesCollapsedisCollapsed = true}}
当然还有些其他便于设置的参数,例如:
private var maxLinesCollapsed: Int = 2//默认折叠行数private var isCollapsed: Boolean = falseprivate 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 = spannableStringmovementMethod = LinkMovementMethod.getInstance()super.onMeasure(widthMeasureSpec, heightMeasureSpec)
好了,我们搞定了,完整代码
class ScalingTextView(context: Context, attrs: AttributeSet?) :AppCompatTextView(context, attrs) {private var maxLinesCollapsed: Int = 2private var isCollapsed: Boolean = falseprivate val TAG: String = ScalingTextView::class.java.simpleNameprivate var mOriginText: Stringprivate @ColorInt var mOriginTextColor: Intprivate val DEFAULT_OPEN_SUFFIX = "查看全文"private val DEFAULT_CLOSE_SUFFIX = "收起全文"private val ellipsis = "..."private var spannableString: SpannableString? = nullinit {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_VALUEisCollapsed = false} else {// 折叠文本maxLines = maxLinesCollapsedisCollapsed = 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_SUFFIXspannableString = 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 = spannableStringmovementMethod = LinkMovementMethod.getInstance()super.onMeasure(widthMeasureSpec, heightMeasureSpec)}else if (layout != null && !isCollapsed) {val newText = mOriginText + DEFAULT_CLOSE_SUFFIXspannableString = 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 = spannableStringmovementMethod = LinkMovementMethod.getInstance()super.onMeasure(widthMeasureSpec, heightMeasureSpec)}}}
xml里使用,这里默认是展开的,你们默认隐藏的话自己实现
<com.github.demo.wight.ScalingTextViewandroid: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"" />
相关文章:
Android实现超出固定行数折叠文字“查看全文“、“收起全文“
先上效果图 分析问题 网上有很多关于这个的代码,实现都过于复杂了,github上甚至还看到一篇文章600多行代码,结果一跑起来全是bug。还是自己写吧!!! 如果我们需要换行的"查看全文"、"收起全…...
Python上楼梯问题:递归解法探究(斐波那契变种)(记忆化递归)
文章目录 上楼梯问题:递归解法探究问题定义解决方案1. 递归2. 记忆化递归关于python memo{}默认参数和字典的语法语法功能版本信息注意事项 结论 上楼梯问题:递归解法探究 在这篇文章中,将对上楼梯问题进行深入探讨。上楼梯问题是一种常见的…...
AI重新定义音视频生产力“新范式”
// 编者按:AIGC无疑是当下的热门话题和场景。面对AI带来的技术变革和算力挑战,该如何应对?LiveVideoStackCon 2023上海站邀请到了网心科技副总裁武磊为我们分享网心在面对AI应用场景和业务需求下的实践经验。 文/武磊 编辑/LiveVideoStack …...
Jmeter生成可视化的HTML测试报告
Jmeter也是可以生成测试报告的。 性能测试工具Jmeter由于其体积小、使用方便、学习成本低等原因,在现在的性能测试过程中,使用率越来越高,但其本身也有一定的缺点,比如提供的测试结果可视化做的很一般。 不过从3.0版本开始&…...
5G技术与其对智能城市、物联网和虚拟现实领域的影响
随着第五代移动通信技术(5G)的到来,我们即将迈向一个全新的数字化世界。5G技术的引入将带来更高的速度、更低的延迟和更大的连接性,推动了智能城市、物联网和虚拟现实等领域的发展。 首先,5G技术将带来超越以往的网络速…...
leetcode做题笔记88. 合并两个有序数组
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。 注意:最终,合并后数组…...
stm32开关控制led灯泡(附Proteus电路图)
说明:我的灯泡工作电压2V,电流设置为10um,注意了不是10毫安时微安啊,要不然电流太小亮不起来的。 2:我用的开关不是按钮button而是switch, 3:PB0,PB1默认都是低电平,采用了PULLDOWN模式,如果设…...
win10 wsl ubuntu 更换版本为18.04 apt换国内源Python换国内源;默认root
控制面板里面应用模块找到Ubuntu,可以卸载或者移动到其他盘。 Microsoft 应用程序 - ubuntu https://apps.microsoft.com/store/search/ubuntu?hlzh-cn&glcn&rtc1 选择想要的版本安装。 cp /etc/apt/sources.list /etc/apt/sources.list.bak nano /etc/ap…...
C++ Primer 第1章 开始
C Primer 第1章 开始 1.1 编写一个简单的C程序1.1.1 编译、运行程序一、程序源文件命名约定二、从命令行运行编译器 练习 1.2 初识输入输出一、标准输入输出对象二、一个使用IO库的程序三、向流写入数据四、使用标准库中的名字五、从流读取数据六、完成程序 1.3 注释简介一、C中…...
【STM32 学习】电源解析(VCC、VDD、VREF+、VBAT)
VCC电源电压GND电源供电负电压(通常接地)VDD模块工作正电压VSS模块工作负电压VREFADC参考正电压VREF-ADC参考负电压VBAT电池或其他电源供电VDDA模拟供电正电压VSSA模拟供电负电压 一、VCC(供电电压) VCC是指芯片的电源电压&#…...
C语言实例_解析GPS源数据
一、GPS数据格式介绍 GPS(全球定位系统)数据格式常见的是NMEA 0183格式,NMEA 0183格式是一种用于导航设备间传输数据的标准格式,定义了一套规范,使得不同厂商的设备可以通过串行通信接口(常见的是RS-232&a…...
LVS+Keepalived
Keepalived概述: keepalived软件 就是通过vrrp协议实现高可用功能 vrrp通信原理: vrrp就是虚拟路由冗余协议,它的出现就是为了解决静态路由的单点故障vrrp是通过一种竞选的一种协议机制将路由交给某台vrrp路由器vrrp用ip多播的方式【多播地…...
uni-app根据经纬度逆解析详细地址
uni-app中的getLocation()方法可以获取到用户当前的地理位置(经纬度)、速度。 但是返回参数中的address在app中才会显示,小程序中不会显示,所以我们需要进行逆解析其地址,解析出它的地址信息。 1.首先要在腾讯位置服务…...
【数据结构】吃透单链表!!!(详细解析~)
目录 前言:一.顺序表的缺陷 && 介绍链表1.顺序表的缺陷2.介绍链表(1)链表的概念(2)链表的结构(3)链表的功能 二.单链表的实现1.创建节点的结构2.头文件函数的声明3.函数的实现ÿ…...
Linux 安全技术和防火墙
目录 1 安全技术 2 防火墙 2.1 防火墙的分类 2.1.1 包过滤防火墙 2.1.2 应用层防火墙 3 Linux 防火墙的基本认识 3.1 iptables & netfilter 3.2 四表五链 4 iptables 4.2 数据包的常见控制类型 4.3 实际操作 4.3.1 加新的防火墙规则 4.3.2 查看规则表 4.3.…...
Mac 开发 Tang Nano FPGA 指南(使用终端和使用 VS Code 和插件,适用所有 Gowin FPGA)
最近收到了一个 Tang nano 9K FPGA开发板,就想借此机会研究一下。 官方文档里介绍如果想使用高云的 FPGA,就需要使用 GOWIN IDE,但是需要申请 license 提交一堆资料,我是别人送的就不太方便让别人弄。加上 IDE 其实并不是很适合学…...
基于深度学习的铁路异物侵限检测算法研究_整体认知感觉欠点意思,但是有一个新的变形卷积-Octave 卷积
相比于其他的交通运输方式,铁路运输具有准时性高、连续性强、速度快、运输量大、运输成本低以及安全可靠等优点。同时由于国家高速铁路网络建设的不断推进,铁路运输逐渐成为我国客运与货运的主要运输方式。虽然铁路运输为人们出行和货物运输带来的极大的…...
Spring项目使用Redis限制用户登录失败的次数以及暂时锁定用户登录权限
文章目录 背景环境代码实现0. 项目结构图(供参考)1. 数据库中的表(供参考)2. 依赖(pom.xml)3. 配置文件(application.yml)4. 配置文件(application-dev.yml)5…...
2023.8 - java - 变量类型
在Java语言中,所有的变量在使用前必须声明。声明变量的基本格式如下: type identifier [ value][, identifier [ value] ...] ; 格式说明: type -- 数据类型。identifier -- 是变量名,可以使用逗号 , 隔开来声明多个同类型变量…...
【Kubernetes】Kubernetes的Pod控制器
Pod控制器 一、Pod 控制器的概念1. Pod 控制器及其功用2. Pod 控制器有多种类型2.1 ReplicaSet2.2 Deployment2.3 DaemonSet2.4 StatefulSet2.5 Job2.6 Cronjob 3. Pod 与控制器之间的关系 二、Pod 控制器的使用1. Deployment2. SatefulSet2.1 为什么要有headless?2…...
AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...
