Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple issues with current solution #4

Open
Loic-Dumas opened this issue Apr 13, 2023 · 2 comments
Open

Multiple issues with current solution #4

Loic-Dumas opened this issue Apr 13, 2023 · 2 comments

Comments

@Loic-Dumas
Copy link

Loic-Dumas commented Apr 13, 2023

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.

@jboxx
Copy link

jboxx commented May 24, 2023

it's recycled overtime when I use RecyclerView, it won't showing the ellipsis again @Loic-Dumas
And since you use onLayout instead of onMeasure, does it will degrade the performance?

@Loic-Dumas
Copy link
Author

Hello @jboxx,
In my use case, I don't use RecyclerView and haven't tested on it.
After some time in production, I didn't noticed degradation in app performance using onLayout().

But if maybe it's better to still do the ellipsize in onMeasure().
The code can be easily shift to this overridden method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants