Last active
May 16, 2020 14:10
-
-
Save ninalofrese/885d45f942e1d696e02b2435c6a65e98 to your computer and use it in GitHub Desktop.
Utility code to make a TextView expands on click, with "view more" suffix and a label text prefix.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| fun setupView { | |
| textViewA.setAsExpandable(text, viewOrGroupToHide, 3, context.getString(R.string.label_text)) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import android.animation.ObjectAnimator | |
| import android.text.SpannableStringBuilder | |
| import android.text.TextUtils | |
| import android.util.Log | |
| import android.view.View | |
| import android.widget.TextView | |
| import androidx.core.content.ContextCompat | |
| /** | |
| * Created by Nina Lofrese @ninalofrese on 10/05/20. | |
| */ | |
| // This method not only creates the Expandable, as it hides the entire view or group if the text is null or blank, | |
| // and place a label before the text | |
| fun TextView.setAsExpandable(text: String?, view: View? = null, maxLines: Int, label: String = "") { | |
| val labelColor = ContextCompat.getColor(context, R.color.black_2d2d2d) | |
| if (text.isNullOrBlank()) { | |
| if (view == null) this.setGone() else view.setGone() | |
| } else { | |
| //runs assynchronously and make sure the code that measures is running after first measure | |
| post { | |
| this.afterMeasured { | |
| setExpandText(label, text, maxLines, labelColor) | |
| setExpandAction(label, text, labelColor) | |
| } | |
| } | |
| } | |
| } | |
| private fun TextView.setExpandAction(label: String = "", text: String, labelColor: Int? = null) { | |
| //only expands on click (once) | |
| setOnClickListener { | |
| val animation: ObjectAnimator = ObjectAnimator.ofInt(this, "maxLines", 30) | |
| this.text = getFullText(label, text, labelColor) | |
| animation.setDuration(200L).start() | |
| } | |
| } | |
| private fun TextView.setExpandText(labelName: String = "", text: String, collapseLines: Int, labelColor: Int?) { | |
| //avoid Exception for XMLs attributes | |
| this.maxLines = collapseLines.takeUnless { it <= 0 } ?: 1 | |
| this.ellipsize = null | |
| //the text to be measured is one with the labelText too | |
| val allText = getFullText(labelName, text, labelColor) | |
| this.text = allText | |
| val builder = SpannableStringBuilder() | |
| try { | |
| if (lineCount > collapseLines) { | |
| val lastCharShown = layout.getLineVisibleEnd(maxLines - 1) | |
| this.maxLines = collapseLines | |
| val moreString = " ${context.getString(R.string.read_more)}" | |
| if (labelName.isNotBlank()) builder.append("$labelName ".styleSpan(bold = true, color = labelColor)) | |
| builder.append(text.substring(0, lastCharShown - moreString.length - 7 - labelName.length)) | |
| builder.append(moreString.styleSpan(color = labelColor)) | |
| this.text = builder | |
| } | |
| } catch (e: IndexOutOfBoundsException) { | |
| //if something happens and launch an Exception, use the "default" ellipsis to the text | |
| Log.w("ExpandableTextView", "Could not add readMore suffix. Using default ellipsis instead. ${e.cause}") | |
| this.maxLines = collapseLines | |
| this.ellipsize = TextUtils.TruncateAt.END | |
| } | |
| } | |
| private fun getFullText(label: String, text: String, labelColor: Int?): SpannableStringBuilder { | |
| val builder = SpannableStringBuilder() | |
| if (label.isNotBlank()) builder.append("$label ".styleSpan(bold = true, color = labelColor)) | |
| builder.append(text) | |
| return builder | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| fun View.setGone() { | |
| this.visibility = View.GONE | |
| } | |
| inline fun <T : View> T.afterMeasured(crossinline f: T.() -> Unit) { | |
| viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { | |
| override fun onGlobalLayout() { | |
| f() | |
| viewTreeObserver.removeOnGlobalLayoutListener(this) | |
| } | |
| }) | |
| } | |
| fun String.styleSpan(bold: Boolean? = null, color: Int? = null): SpannableString { | |
| val span = SpannableString(this) | |
| if (bold.notNullOrFalse()) span.setSpan(StyleSpan(Typeface.BOLD), 0, this.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) | |
| color?.let { span.setSpan(ForegroundColorSpan(it), 0, this.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } | |
| return span | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment