Android ImageSpan 图文混排

本文示例项目地址

对于 Android 开发者,部分场景下需要实现图文混排的排版方式(即在一个UI 控件中同时显示图片与文字),较为常见的场景如社交类 App 中对文字与表情的展示。

实现图文混排,主要有以下方法:

  1. 使用 WebView 加载 HTML
  2. 使用 Html.fromHtml(String source, int flags)获取 Spanned 对象后,通过 TextView 展示
  3. 使用 ImageSpan 展示图片

实际上方法 2 与方法 3 是相似的,而方法 3 对于不熟悉 HTML 的 Android 开发者更为友好并且提供了更高的自由度。本文主要分析方法 3 的基本使用与自定义绘制。

基本使用

ImageSpanDynamicDrawableSpan 的直接子类,开发者通过 SpannableString/SpannableStringBuildersetSpan() 方法将字符串的指定部分设置为由 ImageSpan 构造方法传入的图片。ImageSpan 的基本使用可以参考如下代码,完整代码可以查看示例项目的 BasicImageSpanActivity 类。

Drawable fuDrawable = getResources().getDrawable(R.drawable.image_fu);
fuDrawable.setBounds(0, 0, textView.getLineHeight(),textView.getLineHeight());
ImageSpan imageSpan = new ImageSpan(fuDrawable);
spannableString.setSpan(imageSpan, start, end, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
textView.setText(spannableString);
复制代码

对齐方式

图片与文字的位置关系需要通过设置 ImageSpan 的对齐方式来实现。通过在 ImageSpan 的构造方法中传入 verticalAlignment 参数可以实现对图片与文字的纵向对齐方式的设置。ImageSpan 为开发者提供了两种对齐方式:

  1. ALIGN_BOTTOM:图片底部与所在行底部对齐。
  2. ALIGN_BASELINE:图片底部与文字基线对齐。文字基线(baseline)是字体排印学的概念之一,其具体含义可以参考 维基百科的基线词条。同时,开发者可以将其简单的理解为文字的“重心”位置。

自定义绘制

对于图文混排,不少场景需要图片与文字居中对齐(即图片的中线与文字的中线重合),而 ImageSpan 并未提供这样的对齐方式。开发者可以通过重写(OverrideImageSpan 父类 DynamicDrawableSpandraw 方法来实现自己的绘制逻辑,从而实现图片与文字的居中对齐。DynamicDrawableSpandraw 方法的实现如下:

    @Override
    public void draw(Canvas canvas, CharSequence text,
                     int start, int end, float x, 
                     int top, int y, int bottom, Paint paint) {
        Drawable b = getCachedDrawable();
        canvas.save();
        
        int transY = bottom - b.getBounds().bottom;
        if (mVerticalAlignment == ALIGN_BASELINE) {
            transY -= paint.getFontMetricsInt().descent;
        }

        canvas.translate(x, transY);
        b.draw(canvas);
        canvas.restore();
    }
复制代码

方法中 bottom 为图片所在行底部坐标(以 TextView 左上角为原点),DynamicDrawableSpan 根据 mVerticalAlignment 的值,使用 canvastranslate 方法,将画布移动对应的距离(ALIGN_BOTTOM 移动 bottomdrawable 底部差值的距离,ALIGN_BASELINE 在前者的基础上减去 descent 值,descent 为基线到行底部的距离),来实现对应的对齐方式。开发者可以参考 DynamicDrawSpan 的实现方式,来实现居中对齐乃至更多的绘制逻辑。居中对齐的实现代码可以参考如下代码,完整代码可以参考示例项目中 CustomImageSpandraw 方法。

    @Override
    public void draw(Canvas canvas, CharSequence text, int start, int end,
                     float x, int top, int y, int bottom, Paint paint) {
        Drawable b = getDrawable();
        Paint.FontMetricsInt fm = paint.getFontMetricsInt();
        int transY = (y + fm.descent + y + fm.ascent) / 2 - (b.getBounds().bottom + b.getBounds().top) / 2;
        canvas.save();
        canvas.translate(x, transY);
        b.draw(canvas);
        canvas.restore();
    }
复制代码

代码中 y 为基线的纵坐标(以 TextView 左上角为坐标原点),fm.descent 为基线至文字底部的距离(为正值),fm.ascent 为基线至文字顶部的距离(为负值),y + fm.descent 的值为文字底部纵坐标,y + fm.ascent 的值为文字顶部纵坐标,二者相加除以2则得到了文字纵向中点的纵坐标,(b.getBounds().bottom + b.getBounds().top) / 2 则为 drawable 绘制区域纵向中点的纵坐标,而二者的差值即为实现居中对齐画布的纵向偏移。

为验证代码的效果,笔者使用一个高16、宽48的黑色(#000000)色块作为传入 ImageSpandrawable,展示了在不同对齐方式下图文混排的效果,该示例的完整代码可以参考示例项目的 CustomImageSpanActivity 类,代码的运行效果如下图:

为了方便读者对比不同对齐方式,截图中开启了开发者选项中的显示布局边界

由于开发者可以在 DynamicDrawableSpandraw 方法中实现自己的绘制逻辑,使用该方案来实现图文混排给予了开发者极大的自由度,开发者可以对与文字混排的图片进行更为细致的排版,本文仅以较为常见居中对齐作为示例,相信读者可以实现更多更好的图文混排绘制逻辑。

转载于:https://juejin.im/post/5a5f5e095188253dc33203b4

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值