先上效果图
分析问题
网上有很多关于这个的代码,实现都过于复杂了,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_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"" />