###### tags: `Android` `Kotlin` `View`
# ExpandTextView
有時候我們會希望元件預設的規模是小的,當使用者想更了解內容再顯示出來
例如留言訊息的文字很多,但使用者不一定想全盤接受,而是想要的再打開來看
那這樣的情境該怎麼做呢? 就是打造一個可以擴展的TextView,以下將介紹如何實現
## 創建類別 ExpandTextView
```kotlin=
class ExpandTextView : AppCompatTextView {
/**true:展開 false:收起 */
private var expandState: Boolean = false
var mCallback: Callback? = null
var ellipsizeText = ""
var expandText = "展開"
var expandTextColor: Int = Color.parseColor("#1C7FFD")
var collapseText = "收起"
var collapseTextColor: Int = Color.parseColor("#1C7FFD")
var collapseEnable = false
var underlineEnable = false
constructor(context: Context): super(context)
constructor(context: Context, attributes: AttributeSet): super(context, attributes)
constructor(context: Context, attributes: AttributeSet, defStyleAttr: Int): super(context, attributes, defStyleAttr)
init {
movementMethod = LinkMovementMethod.getInstance()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
// 文字計算輔助工具
if (text.isEmpty())
setMeasuredDimension(measuredWidth, measuredHeight)
val sl = StaticLayout.Builder.obtain(text, 0, text.length, paint, measuredWidth - paddingLeft - paddingRight)
.setAlignment(Layout.Alignment.ALIGN_CENTER)
.setLineSpacing(0f, 1f)
.setIncludePad(true).build()
// 總行數
var lineCount = sl.lineCount
if (lineCount > maxLines) {
if (expandState) {
//收起功能
if (collapseEnable) {
// 收起文案和源文字组成的新的文字
val newEndLineText = text.toString() + collapseText
val spannableString = SpannableString(newEndLineText)
spannableString.setSpan(object : ClickableSpan() {
override fun onClick(widget: View?) {
mCallback?.onCollapseClick()
}
// 設定下畫線
override fun updateDrawState(ds: TextPaint) {
super.updateDrawState(ds)
ds.isUnderlineText = underlineEnable
}
}, newEndLineText.length - collapseText.length, newEndLineText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
spannableString.setSpan(ForegroundColorSpan(collapseTextColor), newEndLineText.length - collapseText.length, newEndLineText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
text = spannableString
}
mCallback?.onExpand()
} else {
lineCount = maxLines
// 省略文字和展開文案的寬度
val dotWidth = paint.measureText(ellipsizeText + expandText)
// 找出顯示最後一行的文字
val start = sl.getLineStart(lineCount - 1)
val end = sl.getLineEnd(lineCount - 1)
val lineText = text.substring(start, end)
// 將最後一行最後文字替換成 ellipsizeText和expandText
var endIndex = 0
for (i in lineText.length - 1 downTo 0) {
val str = lineText.substring(i, lineText.length)
// 找出文字寬度大於 ellipsizeText 的字符
if (paint.measureText(str) >= dotWidth) {
endIndex = i
break
}
}
// 新的文字
val newEndLineText = text.substring(0, start) + lineText.substring(0, endIndex) + ellipsizeText + expandText
val spannableString = SpannableString(newEndLineText)
//展開文字監聽
spannableString.setSpan(object : ClickableSpan() {
override fun onClick(widget: View?) {
mCallback?.onExpandClick()
}
//設定下畫線
override fun updateDrawState(ds: TextPaint) {
super.updateDrawState(ds)
ds.isUnderlineText = underlineEnable
}
}, newEndLineText.length - expandText.length, newEndLineText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
spannableString.setSpan(expandTextColor, newEndLineText.length - expandText.length, newEndLineText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
// 最终文字
text = spannableString
mCallback?.onCollapse()
}
} else
mCallback?.onLoss()
// 重新計算高度
var lineHeight = 0
for (i in 0 until lineCount) {
val lineBound = Rect()
sl.getLineBounds(i, lineBound)
lineHeight += lineBound.height()
}
lineHeight = (paddingTop + paddingBottom + lineHeight * lineSpacingMultiplier).toInt()
setMeasuredDimension(measuredWidth, lineHeight)
}
/**
* 設置文字與顯示狀態
* @param text
* @param expanded true:開啟、false:收起
* @param callback
*/
fun setText(text: String, expanded: Boolean, callback: Callback?) {
expandState = expanded
mCallback = callback
// 設置文字(必要),否則onMeasure寬度測量不正確
setText(text)
}
/**
* 展開收起狀態變化
* @param expanded
*/
fun setChanged(expanded: Boolean) {
expandState = expanded
requestLayout()
}
}
interface Callback {
/**
* 展開狀態
*/
fun onExpand()
/**
* 收起狀態
*/
fun onCollapse()
/**
* 行数小于最小行数,不满足展开或者收起条件
*/
fun onLoss()
/**
* 觸發展開
*/
fun onExpandClick()
/**
* 觸發收起
*/
fun onCollapseClick()
}
```
## 使用方式
```kotlin=
//需要在xml設定最大行數
holder.tv_msg.ellipsizeText = "..." //省略文字
holder.tv_msg.expandText = context.resources.getString(R.string.continue_read) //擴展的顯示文字
holder.tv_msg.expandTextColor = context.resources.getColor(R.color.colorAccent, null) //擴展文字顏色
holder.tv_msg.setText(getItem(position).content, false, null) //設定TextView文字及是否預設為擴展狀態
holder.tv_msg.underlineEnable = true //設定擴展與收起文字是否要有底線
//監聽事件
holder.tv_msg.setText("", false, object : Callback {
override fun onCollapse() {}
override fun onExpand() {}
override fun onLoss() {}
override fun onCollapseClick() {}
override fun onExpandClick() {}
})
```
> https://github.com/yinjinyj/ExpandTextView/blob/master/library/src/main/java/com/yinjin/expandtextview/ExpandTextView.kt