Skip to content

Multiple issues with current solution #4

@Loic-Dumas

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.

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