Align text around ImageSpan center vertical

前端 未结 9 1808
抹茶落季
抹茶落季 2020-11-28 23:59

I have an ImageSpan inside of a piece of text. What I\'ve noticed is that the surrounding text is always drawn at the bottom of the text line -- to be more precise, the size

相关标签:
9条回答
  • 2020-11-29 00:26

    I got a working solution by creating a class that inherits from ImageSpan.

    Then modified draw implementation from DynamicDrawableSpan. At least this implementation works when my image height is less than font height. Not sure how this works for bigger images like yours.

    @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();
    }
    

    Also had to reuse implementation from DynamicDrawableSpan as it was private.

    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;
    

    And this is how I use it as static method that inserts image in front of the text.

    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;
    

    Maybe not a good practice there considering localization, but works for me. To set images in the middle of the text, you'd naturally need to replace tokens in text with spans.

    0 讨论(0)
  • 2020-11-29 00:27

    My answer tweaks the misaka-10032 answer. work perfect!

    public static class CenteredImageSpan extends ImageSpan {
        private WeakReference<Drawable> mDrawableRef;
    
        CenteredImageSpan(Context context, final int drawableRes) {
            super(context, drawableRes);
        }
    
        public CenteredImageSpan(@NonNull Drawable d) {
            super(d);
        }
    
        @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 transY = top + (bottom - top - b.getBounds().bottom)/2;
            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;
        }
    }
    
    0 讨论(0)
  • 2020-11-29 00:33

    My answer tweaks the first answer. Actually I have tried both two methods above, and I don't think they are really center vertical. It would make the drawable more center if it's placed in between ascent and descent, rather than top and bottom. So as to the second answer, it aligns the center of the drawable to the baseline of the text, rather than the center of that text. Here's my solution:

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

    I also rewrite getSize to keep the FontMetrics of drawable the same as other text, otherwise the parent view won't wrap the content correctly.

    0 讨论(0)
提交回复
热议问题