【Android UI】图片 + 文字展示by SpannableStringBuilder

起源

图片和文字混合展示,比如这么个需求,需要在每段文字的左边要有一个小圆点,(小圆点符号在android系统中并不支持)。
先用TextView的setDrawableLeft,
这里写图片描述

嗯,达到要求,那么换个行看看。

这里写图片描述

喔,糟糕,这个setDrawableLeft是为整个TextView服务的,而不只是为其中某一行准备的哟。

SpannableStringBuilder

为了满足上面说到的需求,使用SpannableStringBuilder的setSpan方法可以将一串文字中的指定字符替换为图片。我先继承SpannableStringBuilder利用setSpan封装出一个appendSpan方法出来。

public class MTSpannableStringBuilder extends SpannableStringBuilder {
    public MTSpannableStringBuilder appendSpan(String text, Object what, int flags) {
        int start = length();
        append(text);
        setSpan(what, start, length(), flags);
        return this;
    }
}
Drawable drawable = getResources().getDrawable(R.drawable.train_round);
        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        //要让图片替代指定的文字就要用ImageSpan
        ImageSpan span = new ImageSpan(drawable,ImageSpan.ALIGN_BASELINE);

        MTSpannableStringBuilder builder = new MTSpannableStringBuilder();
        builder.appendSpan("ha",span, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        TextView tt = (TextView) findViewById(R.id.spanableText);
        builder.append("hahahahahahahhahahahahahahahahahahahahahahahahahaahhahahahahlasjljfljasdfasdfsdfadfasdfasdffsfsfsf");
        tt.setText(builder);

最终的结果是:
这里写图片描述

哈哈,似乎快要接近了,但是圆点并不能垂直方向居中。希望能够将图片居中,需要经历:
* 将图片”拔高”到居中位置,
* 居中位置怎么计算

因为这里ImageSpan是”替换”TextView中文字内容,所以其理应和文字内容的位置保持对齐。请参考下篇:
【Android UI】TextView的垂直方向概念之top,bottom,ascent,descent,baseline

这里写图片描述

如下代码中onDraw先将图片和文字内容的bottom对齐,然后再将图片和文字内容居中对齐,对齐的方式就是通过减去[(fontAscent-fontDecent) - drawableHeight]/2。

public class CenteredImageSpan extends ImageSpan {
    private WeakReference<Drawable> mDrawableRef;

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

    @Override
    public int getSize(Paint paint, CharSequence text,
                       int start, int end,
                       Paint.FontMetricsInt fm) {
        Drawable d = getCachedDrawable();
        Rect rect = d.getBounds();

        if (fm != null) {
            Paint.FontMetricsInt pfm = paint.getFontMetricsInt();
            // keep it the same as paint's fm
            fm.ascent = pfm.ascent;
            fm.descent = pfm.descent;
            fm.top = pfm.top;
            fm.bottom = pfm.bottom;
        }

        return rect.right;
    }

    @Override
    public void draw(@NonNull Canvas canvas, CharSequence text,
                     int start, int end, float x,
                     int top, int y, int bottom, @NonNull Paint paint) {
        Drawable b = getCachedDrawable();
        canvas.save();

        int drawableHeight = b.getIntrinsicHeight();
        int fontAscent = paint.getFontMetricsInt().ascent;
        int fontDescent = paint.getFontMetricsInt().descent;
        int transY = bottom - b.getBounds().bottom +  // align bottom to bottom
                (drawableHeight - fontDescent + fontAscent) / 2;  // align center to center

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

    // Redefined locally because it is a private member from DynamicDrawableSpan
    private Drawable getCachedDrawable() {
        WeakReference<Drawable> wr = mDrawableRef;
        Drawable d = null;

        if (wr != null) {
            d = wr.get();
        }

        if (d == null) {
            d = getDrawable();
            mDrawableRef = new WeakReference<>(d);
        }

        return d;
    }
}

然后得到如下令人满意的结果:
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值