Выровнять текст вокруг вертикали центра ImageSpan

У меня есть ImageSpan внутри части текста. Я заметил, что окружающий текст всегда рисуется в нижней части текстовой строки – точнее, размер текстовой строки растет с изображением, но исходный текст не смещается вверх. Когда изображение заметно больше размера текста, эффект довольно неприглядный.

Вот пример, схема показывает границы TextView : Введите описание изображения здесь

Я пытаюсь, чтобы окружающий текст был центрирован по вертикали относительно отображаемого изображения. Вот такой же образец с синим текстом, показывающим желаемое местоположение:

Введите описание изображения здесь

Вот те ограничения, с которыми я связан:

  • Я не могу использовать составные чертежи. Изображения должны быть показаны между словами.
  • Текст может быть многострочным в зависимости от содержимого. Я не контролирую это.
  • Мои изображения больше, чем окружающий текст, и я не могу уменьшить их размер. В то время как изображение образца выше, чем фактические изображения (чтобы продемонстрировать текущее поведение), фактические изображения все еще достаточно велики, чтобы эта проблема была заметна.

Я попытался использовать атрибут android:gravity="center_vertical" в TextView, но это не имеет никакого эффекта. Я считаю, что это только вертикально центрирует текстовые строки , но внутри текстовой строки текст по-прежнему рисуется внизу.

Мой текущий ход мысли заключается в создании настраиваемого диапазона, который сдвигает базовую линию текста в зависимости от высоты строки и текущего размера текста. Этот диапазон охватывал весь текст, и мне пришлось бы вычислить пересечение с ImageSpan s, чтобы я мог избежать смещения изображений. Это звучит довольно сложно, и я надеюсь, что кто-то может предложить другой подход.

Любая помощь приветствуется!

Solutions Collecting From Web of "Выровнять текст вокруг вертикали центра ImageSpan"

Это может быть немного поздно, но я нашел способ сделать это, независимо от размера изображения. Вам нужно создать класс, расширяющий ImageSpan и переопределить методы getSize() и getCachedDrawable() (нам не нужно менять последнее, но этот метод из DynamicDrawableSpan является закрытым и к нему нельзя получить доступ по-другому из дочернего класса) , В getSize(...) вы можете переопределить способ, которым DynamicDrawableSpan устанавливает восхождение / верхний / спуск / дно линии и достижение того, что вы хотите сделать.

Вот мой пример:

 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, entry, DynamicDrawableSpan.ALIGN_BOTTOM); } public CenteredImageSpan(final Drawable drawable, final int verticalAlignment) { super(drawable, verticalAlignment); } @Override public Rect getBounds() { return getDrawable().getBounds(); } @Override public void draw(final Canvas canvas) { getDrawable().draw(canvas); } // // Following methods are overriden from DynamicDrawableSpan. // // 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; } 

Дайте мне знать, если у вас есть проблемы с этим классом!

Мой ответ изменяет первый ответ. На самом деле я попробовал оба метода выше, и я не думаю, что они действительно вертикальные по центру. Это сделало бы более привлекательным центр, если он был помещен между ascent и descent , а не top и bottom . Что касается второго ответа, он выравнивает центр доступного к базовой линии текста, а не центр этого текста. Вот мое решение:

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

Я также переписываю getSize чтобы сохранить getSize с возможностью рисования так же, как и другой текст, в противном случае родительский вид не будет правильно помещать содержимое.

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

Я получил рабочее решение, создав класс, который наследуется от ImageSpan .

Затем была изменена реализация draw от 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(); } 

Также пришлось повторно использовать реализацию из 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; 

И так я использую его как статический метод, который вставляет изображение перед текстом.

 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; 

Возможно, это не хорошая практика, связанная с локализацией, но работает для меня. Чтобы установить изображения в середине текста, вам, естественно, нужно будет заменить токены в тексте пробелами.

Прочитав исходный код TextView, я думаю, что мы можем использовать baseLine текстовой строки eache, которая является «y». И он будет работать, даже если вы установите 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(); } } 

Моя улучшенная версия: масштабируемые метрики шрифтов увеличены по сравнению с метриками шрифта текста. Таким образом, расстояние между линиями будет правильно вычисляться.

 @Override public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { Drawable d = getCachedDrawable(); Rect rect = d.getBounds(); float drawableHeight = Float.valueOf(rect.height()); if (fm != null) { Paint.FontMetricsInt pfm = paint.getFontMetricsInt(); float fontHeight = pfm.descent - pfm.ascent; float ratio = drawableHeight / fontHeight; fm.ascent = Float.valueOf(pfm.ascent * ratio).intValue(); fm.descent = Float.valueOf(pfm.descent * ratio).intValue(); fm.top = fm.ascent; fm.bottom = fm.descent; }