###### 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