Introduce
Above is a case you may encounter in Android UI programming. It’s a TextView that can expand and shrink content, with “Show more” and “Show less” buttons following right behind the content of TextView. The problem here is the calculation of the number of characters of the string will display in TextView with max lenght allowed, then calculate the length needed to add more dots and the text “Show more” / “Show less” to Add these components to the previously calculated rock sequence
Deployment
To accomplish this function, I will create a custom TextView First, read the full code of it, I will explain in detail below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | class ShowMoreTextView @kotlin.jvm.JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : AppCompatTextView(context, attrs, defStyleAttr) { private var showingLine = 1 private var showMore = "Show more" private var showLess = "Show less" private val threeDot = "…" private var showMoreTextColor = Color.RED private var showLessTextColor = Color.RED private var mainText: String? = null private var isAlreadySet = false private var isCollapse = true init { viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { if (showingLine >= lineCount) return showMoreButton() viewTreeObserver.removeOnGlobalLayoutListener(this) } }) } override fun onFinishInflate() { super.onFinishInflate() mainText = text.toString() } private fun showMoreButton() { val text = text.toString() if (!isAlreadySet) { mainText = text isAlreadySet = true } var showingText = "" var start = 0 var end: Int for (i in 0 until showingLine) { end = layout.getLineEnd(i) showingText += text.substring(start, end) start = end } var specialSpace = 0 var newText: String do { newText = showingText.substring( 0, showingText.length - (specialSpace) ) newText += "$threeDot $showMore" setText(newText) specialSpace++ } while (lineCount > showingLine) isCollapse = true setShowMoreColoringAndClickable() } private fun setShowMoreColoringAndClickable() { val spannableString = SpannableString(text) spannableString.setSpan( object : ClickableSpan() { override fun updateDrawState(paint: TextPaint) { paint.isUnderlineText = false } override fun onClick(view: View) { maxLines = Int.MAX_VALUE text = mainText isCollapse = false showLessButton() } }, text.length - showMore.length, text.length, 0 ) spannableString.setSpan( ForegroundColorSpan(showMoreTextColor), text.length - showMore.length, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) movementMethod = LinkMovementMethod.getInstance() setText(spannableString, BufferType.SPANNABLE) } private fun showLessButton() { val text = "$text $showLess" val spannableString = SpannableString(text) spannableString.setSpan( object : ClickableSpan() { override fun updateDrawState(pain: TextPaint) { pain.isUnderlineText = false } override fun onClick(view: View) { maxLines = showingLine showMoreButton() } }, text.length - showLess.length, text.length, 0 ) spannableString.setSpan( ForegroundColorSpan(showLessTextColor), text.length - (threeDot.length + showLess.length), text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) movementMethod = LinkMovementMethod.getInstance() setText(spannableString, BufferType.SPANNABLE) } fun setShowingLine(lineNumber: Int) { if (lineNumber == 0) return showingLine = lineNumber maxLines = showingLine } fun addShowMoreText(text: String) { showMore = text } fun addShowLessText(text: String) { showLess = text } fun setShowMoreTextColor(color: Int) { showMoreTextColor = color } fun setShowLessTextColor(color: Int) { showLessTextColor = color } } |
Here are the funtions to init the information of ShowMoreTextView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // set maxlengt cho textview, chính là số dòng hiển thị tối đa khi ở chế độ show more fun setShowingLine(lineNumber: Int) { if (lineNumber == 0) return showingLine = lineNumber maxLines = showingLine } // title của button show more fun addShowMoreText(text: String) { showMore = text } // title của button show less fun addShowLessText(text: String) { showLess = text } // color của button show more fun setShowMoreTextColor(color: Int) { showMoreTextColor = color } // color của button show less fun setShowLessTextColor(color: Int) { showLessTextColor = color } } |
First, at init I will add an addOnGlobalLayoutListener to listen to the change of layout when TextView is setText to handle the new string.
1 2 3 4 5 6 7 8 9 10 11 | init { viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { if (showingLine >= lineCount) return showMoreButton() viewTreeObserver.removeOnGlobalLayoutListener(this) } }) } |
When there is a change in text, the showMoreButton () function will be called if showingLine <lineCount (The number of lines of the string just set) the showMoreButton () function will be responsible for calculating and cutting the string that can be displayed with the current showingLine. then add a dot and a SpannableString “Show more” and finally setText the opposite for this TextView itself.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | private fun showMoreButton() { val text = text.toString() if (!isAlreadySet) { mainText = text isAlreadySet = true } var showingText = "" var start = 0 var end: Int for (i in 0 until showingLine) { end = layout.getLineEnd(i) showingText += text.substring(start, end) start = end } var specialSpace = 0 var newText: String do { newText = showingText.substring( 0, showingText.length - (specialSpace) ) newText += "$threeDot $showMore" setText(newText) specialSpace++ } while (lineCount > showingLine) isCollapse = true setShowMoreColoringAndClickable() } |
When the “Show more” button is clicked, it will call the showLessbutton () function: this function is simpler, just take the original string set and add Spannable “Show less”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | private fun showLessButton() { val text = "$text $showLess" val spannableString = SpannableString(text) spannableString.setSpan( object : ClickableSpan() { override fun updateDrawState(pain: TextPaint) { pain.isUnderlineText = false } override fun onClick(view: View) { maxLines = showingLine showMoreButton() } }, text.length - showLess.length, text.length, 0 ) spannableString.setSpan( ForegroundColorSpan(showLessTextColor), text.length - (threeDot.length + showLess.length), text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) movementMethod = LinkMovementMethod.getInstance() setText(spannableString, BufferType.SPANNABLE) } |
Use
Use in Activity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <com.sun.sowmoretextview.ShowMoreTextView android:id="@+id/show_more" android:textSize="18sp" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" android:text="@string/content"/> </androidx.constraintlayout.widget.ConstraintLayout> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val tv = findViewById<ShowMoreTextView>(R.id.show_more) tv.apply { setShowingLine(4) setShowLessTextColor(Color.BLUE) setShowMoreTextColor(Color.BLUE) addShowLessText("Show less") addShowMoreText("Show more") } } } |
Hope this article will be helpful to you, thanks for watching