Skip to content

Multiple issues with current solution #4

Description

@Loic-Dumas

I noticed multiple issues with the 1.0.0 implementation.

  1. If the text contains "...", before the final ellipsis, the custom ellipsis is added in the text, not at the end.
    Example :
    Capture d’écran 2023-04-13 à 11 33 13

  2. As mentioned in issue Not working 100% #1

availableTextWidth = (availableScreenWidth - paint.measureText(ellipsis)) * maxLines

maxLines should only apply to the availableScreenWidth and not the ellipsis width. So the line should be :

availableTextWidth = availableScreenWidth  * maxLines - paint.measureText(ellipsis)

Otherwise, the more line there is, the ellispis will be more distant to the end of last line.

  1. Anyway, the way ellipsizedText is computed with TextUtils.ellipsize(...) works only on single line. With multiple line, when computing.
availableTextWidth = (availableScreenWidth - paint.measureText(ellipsis)) * maxLines
ellipsizedText = TextUtils.ellipsize(text, paint, availableTextWidth, ellipsize)

this code doesn't consider that the text is not justified. So every end of line will take spaces, which will be reported on the last line. So the ellipsis can be ellipsed it self by TextView.
Example :
Capture d’écran 2023-04-13 à 11 32 52

Based on your solution, I wrote this version of EllipsizedTextView which fix previously mentioned issues.
I reused TextUtils.ellipsize() but only on the last line retrieved from the layout, to avoid issue 3.
Also, instead of using overriding onMeasure(), I overridden onLayout()
Note : Only Ellipsize end is handled.

class EllipsizedTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : AppCompatTextView(context, attrs, defStyleAttr) {

    private var ellipsis = getDefaultEllipsis().toString()
    private var ellipsisColor = getDefaultEllipsisColor()

    private val ellipsisSpannable: SpannableString
    private val spannableStringBuilder = SpannableStringBuilder()

    init {

        if (ellipsize != TextUtils.TruncateAt.END) {
            throw java.lang.IllegalStateException("Only end ellipsize is handled.")
        }

        if (attrs != null) {
            val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.EllipsizedTextView, 0, 0)
            ellipsis = typedArray.getString(R.styleable.EllipsizedTextView_ellipsis) ?: getDefaultEllipsis().toString()
            ellipsisColor = typedArray.getColor(R.styleable.EllipsizedTextView_ellipsisColor, getDefaultEllipsisColor())
            typedArray.recycle()
        }

        ellipsisSpannable = SpannableString(ellipsis)
        ellipsisSpannable.setSpan(ForegroundColorSpan(ellipsisColor), 0, ellipsis.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        if (changed && layout.lineCount >= maxLines) {

            val lastLineNb = maxLines - 1
            val ellipsisCount = layout.getEllipsisCount(lastLineNb)

            if (ellipsisCount > 0) {
                val availableScreenWidth = measuredWidth - compoundPaddingLeft.toFloat() - compoundPaddingRight.toFloat()
                val lastLineAvailableTextWidth = availableScreenWidth - paint.measureText(ellipsis)

                val lastLineStart = layout.getLineStart(lastLineNb)
                val lastLineEnd = layout.getLineEnd(lastLineNb)
                val textWithoutLastLine = text.subSequence(0, lastLineStart)
                val lastLineText = text.subSequence(lastLineStart, lastLineEnd)
                val ellipsizedLasLine = TextUtils.ellipsize(lastLineText, paint, lastLineAvailableTextWidth, ellipsize)

                spannableStringBuilder.clear()
                val result = spannableStringBuilder.append(textWithoutLastLine).append(ellipsizedLasLine).append(ellipsisSpannable)
                text = result
            }
        }
    }

    private fun getDefaultEllipsis(): Char {
        return Typography.ellipsis
    }

    private fun getDefaultEllipsisColor(): Int {
        return textColors.defaultColor
    }

    fun isEllipsized(): Boolean {
        if (TextUtils.isEmpty(text) || text.length < ellipsis.length) {
            return false
        }
        val finalWord = text.subSequence(text.length - ellipsis.length, text.length)
        return TextUtils.equals(finalWord, ellipsis)
    }
}

I didn't opened a pull request, since the project is not building with latest Android Studio.
I still share my solution it may help other.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions