需求:在某些场景下,直接在xml或者代码里面设置android:ellipsize="end"不生效,可以参考这种解决方案,但是会存在截断单词的场景
原理:默认开启自动截取功能autoTruncate = true,可以在xml中设置关闭,需要设置两次super.setText(text, type)文本,第一次设置,是为了拿到的TextView的最大宽度,当文本宽度,大于view的最大宽度,才开始计算截取点,当大于时通过paint.breakText计算截取点,但是计算出的截取点和'...'拼接之后会出现最后一个点显示半个情形,大概原因是因为计算误差导致的,所以在计算的时候多减了一个.的位置距离,后续补充空格处理
使用:同TextView的使用方式一样
自定义View具体实现:
import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup
import androidx.appcompat.widget.AppCompatTextView
/**
* 需要设置两次text才能拿到具体的宽度值
* 相较之前版本算法上有提升
* 考虑到代码入侵性,添加了属性做改造
* autoTruncate = false关闭截取功能,当作一个普通的TextView使用
*/
class EllipsizeEndTextview(context: Context, attributeSet: AttributeSet?) :
AppCompatTextView(context, attributeSet) {
//是否自动截取
private var autoTruncate = true
//内部缓存一个原始值,方便外部获取
private var originalText: CharSequence? = ""
init {
val a = context.obtainStyledAttributes(attributeSet, R.styleable.EllipsizeEndTextView)
autoTruncate = a.getBoolean(R.styleable.EllipsizeEndTextView_autoTruncate, true)
a.recycle()
}
override fun setText(text: CharSequence?, type: BufferType?) {
if (autoTruncate) {
adaptText(text ?: "", type)
} else {
super.setText(text, type)
}
}
private fun adaptText(content: CharSequence, type: BufferType?) {
// 避免重复设置相同内容
if (originalText == content) return
originalText = content
// 先设置一次原始文本,确保宽度可用
super.setText(content, type)
post {
try {
// 计算可用宽度(去除内边距和外边距)
var availableWidth = width - compoundPaddingStart - compoundPaddingEnd
(layoutParams as? ViewGroup.MarginLayoutParams)?.let { lp ->
//某些场景下使用的是外边距,也会影响内部...的显示
availableWidth -= (lp.leftMargin + lp.rightMargin)
}
val textString = (originalText ?: "").toString()
if (paint.measureText(textString) <= availableWidth) {
return@post
}
val ellipsis = "."
val ellipsisWidth = paint.measureText(ellipsis)
//方案一:使用TextUtils.ellipsize(AI推荐,实际在使用的时候会存在少一个计算一个字符串宽度的情形,因为这种方式返回的是unicode形式的省略号,宽度比...要窄)
// val maxTextWidth = (availableWidth - ellipsisWidth * 3).coerceAtLeast(0f)
// //存在计算误差,添加空格补充,空格数量不定,保障撑满即可
// val display = TextUtils.ellipsize(textString, paint, maxTextWidth, TextUtils.TruncateAt.END).toString() + " ".repeat(6)
//方案二:使用paint.breakText(可能会存在特殊字符被截断的情形,比如表情,颜文字等,但是地图那边在输入的时候不支持特殊字符,所以暂不考虑)
//存在计算误差,会导致出现半个点的情况,就多留一个.号的位置,后续用空格补充(和之前count - 1的原理差不多,都是保障.显示完整)
val maxTextWidth2 = (availableWidth - ellipsisWidth * 4).coerceAtLeast(0f)
// 计算能显示的字符数
val count =
paint.breakText(textString, 0, textString.length, true, maxTextWidth2, null)
val display2 = if (count > 0) {
//存在计算误差,添加空格补充,空格数量不定,保障撑满即可
textString.substring(0, count) + ellipsis.repeat(3) + " ".repeat(6)
} else {
if (ellipsisWidth <= availableWidth) ellipsis else ""
}
// Log.d("EllipsizeEndTextview", "adaptText display: $display")
Log.d("EllipsizeEndTextview", "adaptText display2: $display2")
super.setText(display2, type)
} catch (e: Exception) {
Log.e("EllipsizeEndTextview", "adaptText error: ${e.message}")
}
}
}
override fun getText(): CharSequence {
return originalText ?: ""
}
}
涉及到的属性:
<!-- EllipsizeEndTextView 自定义属性 -->
<declare-styleable name="EllipsizeEndTextView">
<!-- 是否自动截取 -->
<attr name="autoTruncate" format="boolean" />
</declare-styleable>