对齐ImageSpan中心垂直周围的文本

Kar*_*uri 39 android text spannable imagespan

我有ImageSpan一段文字.我注意到的是,周围的文本总是在文本行的底部绘制 - 更准确地说,文本行的大小随着图像的增长而增加,但文本的基线不会向上移动.当图像明显大于文本大小时,效果相当难看.

这是一个示例,大纲显示的界限TextView: 在此输入图像描述

我试图让周围的文本相对于正在显示的图像垂直居中.以下是蓝色文本显示所需位置的相同示例:

在此输入图像描述

以下是我受约束的约束:

  • 我不能使用复合drawables.图像必须能够在单词之间显示.
  • 根据内容,文本可以是多行的.我无法控制这一点.
  • 我的图像比周围的文字大,我不能减小它们的大小.虽然上面的示例图像大于实际图像(以演示当前行为),但实际图像仍然足够大,以至于此问题显而易见.

我已尝试android:gravity="center_vertical"在TextView上使用该属性,但这没有任何效果.我认为这只是文本行的垂直居中,但在文本行中,文本仍然在底部绘制.

我目前的思路是创建一个自定义跨度,根据行的高度和当前文本大小来移动文本的基线.这个跨度将包含整个文本,我将不得不计算与ImageSpans 的交集,所以我也可以避免移动图像.这听起来相当令人生畏,我希望有人能提出另一种方法.

任何和所有的帮助表示赞赏!

mis*_*032 63

我的回答调整了第一个答案.实际上我已经尝试了上述两种方法,我认为它们并不是真正的中心垂直方向.这将使更多的绘制中心,如果它放在之间ascentdescent,而不是topbottom.因此,对于第二个答案,它将drawable的中心与文本的基线对齐,而不是该文本的中心.这是我的解决方案:

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;
  }
}
Run Code Online (Sandbox Code Playgroud)

我也重写getSize以保持FontMetrics的drawable与其他文本相同,否则父视图将不会正确地包装内容.

  • 如果图标大于文本,则图标将被剪切 (6认同)
  • 没有在4"设备上工作480x800 hdpi图标在底部文本被切割正确显示,但在xxhdpi设备上工作 (2认同)

juj*_*789 28

可能有点晚了,但无论图像大小如何,我都找到了一种方法.您需要创建一个扩展ImageSpan的类并覆盖这些方法,getSize()并且getCachedDrawable()(我们不需要更改最后一个方法,但此方法DynamicDrawableSpan是私有的,不能以其他方式从子类访问).在getSize(...),你就可以重新定义的方式DynamicDrawableSpan设置直线上升/顶/下降/底层实现你想要做什么.

这是我的班级示例:

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.style.DynamicDrawableSpan;
import android.text.style.ImageSpan;

import java.lang.ref.WeakReference;

public class CenteredImageSpan extends ImageSpan {

    // Extra variables used to redefine the Font Metrics when an ImageSpan is added
    private int initialDescent = 0;
    private int extraSpace = 0;

    public CenteredImageSpan(final Drawable drawable) {
        this(drawable, DynamicDrawableSpan.ALIGN_BOTTOM);
    }

    public CenteredImageSpan(final Drawable drawable, final int verticalAlignment) {
        super(drawable, verticalAlignment);
    }

    @Override
    public void draw(Canvas canvas, CharSequence text,
                     int start, int end, float x,
                     int top, int y, int bottom, Paint paint) {
        getDrawable().draw(canvas);
    }

    // Method used to redefined the Font Metrics when an ImageSpan is added
    @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) {
            // Centers the text with the ImageSpan
            if (rect.bottom - (fm.descent - fm.ascent) >= 0) {
                // Stores the initial descent and computes the margin available
                initialDescent = fm.descent;
                extraSpace = rect.bottom - (fm.descent - fm.ascent);
            }

            fm.descent = extraSpace / 2 + initialDescent;
            fm.bottom = fm.descent;

            fm.ascent = -rect.bottom + fm.descent;
            fm.top = fm.ascent;
        }

        return rect.right;
    }

    // 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;
    }

    private WeakReference<Drawable> mDrawableRef;
}
Run Code Online (Sandbox Code Playgroud)

如果您对该课程有任何疑问,请告诉我!

  • @ElSajko我刚尝试了这个解决方案,它似乎对于大于文本高度的图像工作正常.发布的代码确实有一些错误:删除其中一个构造函数中的"entry"变量,并在`getSize()中将"=>"更改为"> =" (3认同)
  • 由于某种原因,`fm.descent = 3*extraSpace/8 + initialDescent;`工作和`fm.descent = extraSpace/2 + initialDescent;`将文本对齐到顶部.按照本教程btw添加图像和文本:https://guides.codepath.com/android/google-play-style-tabs-using-tablayout (3认同)
  • 请使用paint.getFontMetricsInt()而不是fm.Because因为fm是一个中间变量 (2认同)

boi*_*ter 22

ImageSpan imageSpan = new ImageSpan(d, ImageSpan.ALIGN_BOTTOM) {
                public void draw(Canvas canvas, CharSequence text, int start,
                        int end, float x, int top, int y, int bottom,
                        Paint paint) {
                    Drawable b = getDrawable();
                    canvas.save();

                    int transY = bottom - b.getBounds().bottom;
                    // this is the key 
                    transY -= paint.getFontMetricsInt().descent / 2;

                    canvas.translate(x, transY);
                    b.draw(canvas);
                    canvas.restore();
                }
            };
Run Code Online (Sandbox Code Playgroud)

  • 将 @misaka-10032 的抽奖替换为此抽奖,代码适用于所有分辨率 (2认同)

小智 20

在阅读了TextView的源代码之后,我想我们可以使用"y"的eache文本行的baseLine.即使你设置了lineSpaceExtra它也能工作.

public class VerticalImageSpan extends ImageSpan {

    public VerticalImageSpan(Drawable drawable) {
        super(drawable);
    }

    /**
     * update the text line height
     */
    @Override
    public int getSize(Paint paint, CharSequence text, int start, int end,
                       Paint.FontMetricsInt fontMetricsInt) {
        Drawable drawable = getDrawable();
        Rect rect = drawable.getBounds();
        if (fontMetricsInt != null) {
            Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt();
            int fontHeight = fmPaint.descent - fmPaint.ascent;
            int drHeight = rect.bottom - rect.top;
            int centerY = fmPaint.ascent + fontHeight / 2;

            fontMetricsInt.ascent = centerY - drHeight / 2;
            fontMetricsInt.top = fontMetricsInt.ascent;
            fontMetricsInt.bottom = centerY + drHeight / 2;
            fontMetricsInt.descent = fontMetricsInt.bottom;
        }
        return rect.right;
    }

    /**
     * see detail message in android.text.TextLine
     *
     * @param canvas the canvas, can be null if not rendering
     * @param text the text to be draw
     * @param start the text start position
     * @param end the text end position
     * @param x the edge of the replacement closest to the leading margin
     * @param top the top of the line
     * @param y the baseline
     * @param bottom the bottom of the line
     * @param paint the work paint
     */
    @Override
    public void draw(Canvas canvas, CharSequence text, int start, int end,
                     float x, int top, int y, int bottom, Paint paint) {

        Drawable drawable = getDrawable();
        canvas.save();
        Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt();
        int fontHeight = fmPaint.descent - fmPaint.ascent;
        int centerY = y + fmPaint.descent - fontHeight / 2;
        int transY = centerY - (drawable.getBounds().bottom - drawable.getBounds().top) / 2;
        canvas.translate(x, transY);
        drawable.draw(canvas);
        canvas.restore();
    }

}
Run Code Online (Sandbox Code Playgroud)

  • 这应该被接受ans :)谢谢 (2认同)

pti*_*ili 5

我通过创建一个继承自ImageSpan 的类得到了一个可行的解决方案。

然后修改 DynamicDrawableSpan 的绘制实现。至少当我的图像高度小于字体高度时,此实现有效。不知道这对于像你这样的更大的图像是如何工作的。

@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 bCenter = b.getIntrinsicHeight() / 2;
    int fontTop = paint.getFontMetricsInt().top;
    int fontBottom = paint.getFontMetricsInt().bottom;
    int transY = (bottom - b.getBounds().bottom) -
        (((fontBottom - fontTop) / 2) - bCenter);


    canvas.translate(x, transY);
    b.draw(canvas);
    canvas.restore();
}
Run Code Online (Sandbox Code Playgroud)

还必须重用 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<Drawable>(d);
    }

    return d;
}

private WeakReference<Drawable> mDrawableRef;
Run Code Online (Sandbox Code Playgroud)

这就是我如何使用它作为静态方法,在文本前面插入图像。

public static CharSequence formatTextWithIcon(Context context, String text,
    int iconResourceId) {
    SpannableStringBuilder sb = new SpannableStringBuilder("X");

    try {
        Drawable d = context.getResources().getDrawable(iconResourceId);
        d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); 
        CenteredImageSpan span = new CenteredImageSpan(d); 
        sb.setSpan(span, 0, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        sb.append(" " + text); 
    } catch (Exception e) {
        e.printStackTrace();
        sb.append(text); 
    }

    return sb;
Run Code Online (Sandbox Code Playgroud)

考虑到本地化,也许这不是一个好的做法,但对我来说很有效。要将图像设置在文本中间,您自然需要将文本中的标记替换为跨度。