dingrui1031 / ElipsisArrowTextView

自定义一个多行末尾省略带图标的TextView

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

假如现在有这样的需求,一段多行的文字,限制最多显示到第几行,末尾以省略号结束,并且是要加上一个图标来辅助,以便用户点击跳转。

image.png

显然,TextView并不能轻松地胜任这样的工作。我们需要自定义一个TextView来达到这样的效果。 自然而然地,我们会拿SpannableStringSpannableStringBuilder来实现。

一 开始之前

首先,理清实现这个效果需要解决的点在哪里:

  1. 原本文字在最后一行显示的最终结束位置

  2. 省略号“...”如何加入和计算显示的位置

  3. 图标如何加入和计算显示的位置

有了以上的思路,就可以一个个地开发,写代码啦。

二 计算文字最后一行结束的位置

关于如何测量文字的有关知识点,立马就想到了大佬hencoder.com的自定义view系列。

measureText(): 它测量的是文字绘制时所占用的宽度。

    int indexEnd = getValidLayout().getLineEnd(mMaxLinesOnShrink - 1);
    int indexStart = getValidLayout().getLineStart(mMaxLinesOnShrink - 1);
    int indexEndTrimmed = indexEnd
            - getLengthOfString(mEllipsisHint)
            - (mShowToExpandHint ? getLengthOfString(mGapToExpandHint) : 0);

    if (indexEndTrimmed <= indexStart) {
        indexEndTrimmed = indexEnd;
    }

    int remainWidth = getValidLayout().getWidth() -
            (int) (mTextPaint.measureText(mOrigText.subSequence(indexStart, indexEndTrimmed).toString()) + 0.5) - bitmap1.getWidth();
    float widthTailReplaced = mTextPaint.measureText(getContentOfString(mEllipsisHint)
            + (mShowToExpandHint ? (getContentOfString(mGapToExpandHint)) : ""));

    int indexEndTrimmedRevised = indexEndTrimmed;
    if (remainWidth > widthTailReplaced) {
        int extraOffset = 0;
        int extraWidth = 0;
        while (remainWidth > widthTailReplaced + extraWidth) {
            extraOffset++;
            if (indexEndTrimmed + extraOffset <= mOrigText.length()) {
                extraWidth = (int) (mTextPaint.measureText(
                        mOrigText.subSequence(indexEndTrimmed, indexEndTrimmed + extraOffset).toString()) + 0.5);
            } else {
                break;
            }
        }
        indexEndTrimmedRevised += extraOffset - 1;
    } else {
        int extraOffset = 0;
        int extraWidth = 0;
        while (remainWidth + extraWidth < widthTailReplaced) {
            extraOffset--;
            if (indexEndTrimmed + extraOffset > indexStart) {
                extraWidth = (int) (mTextPaint.measureText(mOrigText.subSequence(indexEndTrimmed + extraOffset, indexEndTrimmed).toString()) + 0.5);
            } else {
                break;
            }
        }
        indexEndTrimmedRevised += extraOffset;
    }

通过以上代码,计算出indexEndTrimmedRevised,这个就是我们的文字最终的末尾位置。

三 省略号“...”

这个比较好做,我们直接在已经处理过的文本后面加上字符串“...”就可以了。

ssbShrink.append(mEllipsisHint);

四 图标如何加入和计算显示的位置

使用ImageSpan,简单的几句代码就可以实现图标一直在最后。

ssbShrink.append("+");
ssbShrink.setSpan(imgSpan1, ssbShrink.length() - 1, ssbShrink.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

可是,当我们运行在手机上时,却发现效果跟想象中的不一样。

image.png

什么!!图标竟然和文字不对齐!

细思之下,ImageSpan有它自己的对齐方式。

  • DynamicDrawableSpan.ALIGN_BASELINE:以基线对齐

  • DynamicDrawableSpan.ALIGN_BOTTOM:以底部对齐

我们看到的实际效果,就是以上对齐方式的结果。

但是,我们在实际开发中布局的时候,很多时候用到的是垂直居中。

(又默默地温习了大佬的课程...)

为了实现对齐效果,需要先了解一下文字的结构。

先解释一个类:Paint.FontMetrics,它表示绘制字体时的度量标准。google的官方api文档对它的字段说明如下:

image

其中,ascent: 字体最上端到基线的距离,为负值。

descent:字体最下端到基线的距离,为正值。

如下图

image

中间那条线就是基线,基线到上面那条线的距离就是ascent,基线到下面那条线的距离就是descent。

因此,我们要让imagespantext对齐,只需把imagespan放到descent线和ascent线之间的中间位置就可以了。实现方式为重写ImageSpan类的draw方法。

最终实现方法如下:

class CenteredImageSpan extends ImageSpan {

    public CenteredImageSpan(Context context, final int drawableRes) {
        super(context, drawableRes);
    }

    @Override
    public void draw(@NonNull Canvas canvas, CharSequence text,
                     int start, int end, float x,
                     int top, int y, int bottom, @NonNull Paint paint) {
        // image to draw
        Drawable b = getDrawable();
        // font metrics of text to be replaced
        Paint.FontMetricsInt fm = paint.getFontMetricsInt();
        int transY = (y + fm.descent + y + fm.ascent) / 2
                - b.getBounds().bottom / 2;

        canvas.save();
        canvas.translate(x, transY);
        b.draw(canvas);
        canvas.restore();
    }
}

解决了以上几点问题之后,最终得到了我们想要的实现效果。虽然这个要实现的效果不复杂,但是也是涉及到了文字的测量、ImageSpan 等几个知识点,认真去对待就会发现还是收获很多。

About

自定义一个多行末尾省略带图标的TextView

License:Apache License 2.0


Languages

Language:Java 100.0%