android - Alinee el texto alrededor del centro vertical de ImageSpan
spannable (8)
Después de leer el código fuente de TextView, creo que podemos usar la línea base de la línea de texto eache que es "y". Y funcionará incluso si configura 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();
}
}
Tengo un
ImageSpan
dentro de un fragmento de texto.
Lo que he notado es que el texto circundante siempre se dibuja en la parte inferior de la línea de texto; para ser más precisos, el tamaño de la línea de texto crece con la imagen pero la línea de base del texto no se desplaza hacia arriba.
Cuando la imagen es notablemente más grande que el tamaño del texto, el efecto es bastante desagradable.
Aquí hay una muestra, el esquema muestra los límites de
TextView
:
Estoy intentando que el texto circundante se centre verticalmente con respecto a la imagen que se muestra. Aquí está la misma muestra con texto azul que muestra la ubicación deseada:
Aquí están las restricciones que estoy obligado por:
- No puedo usar elementos dibujables compuestos. Las imágenes deben poder mostrarse entre palabras.
- El texto puede ser multilínea dependiendo del contenido. No tengo control sobre esto.
- Mis imágenes son más grandes que el texto circundante y no puedo reducir su tamaño. Si bien la imagen de muestra anterior es más grande que las imágenes reales (para demostrar el comportamiento actual), las imágenes reales siguen siendo lo suficientemente grandes como para que este problema sea notable.
Intenté usar el atributo
android:gravity="center_vertical"
en TextView, pero esto no tiene ningún efecto.
Creo que esto solo centra verticalmente las
líneas de
texto, pero dentro de la línea de texto el texto todavía se dibuja en la parte inferior.
Mi línea de pensamiento actual es crear un espacio personalizado que cambie la línea de base del texto en función de la altura de la línea y el tamaño del texto actual.
Este lapso abarcaría todo el texto, y tendría que calcular la intersección con
ImageSpan
s para evitar también el desplazamiento de las imágenes.
Esto suena bastante desalentador y espero que alguien pueda sugerir otro enfoque.
¡Cualquier ayuda es apreciada!
Esta solución funciona Lo he probado y lo estoy usando por algún tiempo. No considera el ascenso y el descenso, pero alinea el dibujo en el centro.
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.text.style.ImageSpan;
import java.lang.ref.WeakReference;
public class CustomImageSpan extends ImageSpan {
/**
* A constant indicating that the center of this span should be aligned
* with the center of the surrounding text
*/
public static final int ALIGN_CENTER = -12;
private WeakReference<Drawable> mDrawable;
private int mAlignment;
public CustomImageSpan(Context context, final int drawableRes, int alignment) {
super(context, drawableRes);
mAlignment = alignment;
}
@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();
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) {
if (mAlignment == ALIGN_CENTER) {
Drawable cachedDrawable = getCachedDrawable();
canvas.save();
//Get the center point and set the Y coordinate considering the drawable height for aligning the icon vertically
int transY = ((top + bottom) / 2) - cachedDrawable.getIntrinsicHeight() / 2;
canvas.translate(x, transY);
cachedDrawable.draw(canvas);
canvas.restore();
} else {
super.draw(canvas, text, start, end, x, top, y , bottom, paint);
}
}
// Redefined locally because it is a private member from DynamicDrawableSpan
private Drawable getCachedDrawable() {
WeakReference<Drawable> wr = mDrawable;
Drawable d = null;
if (wr != null) {
d = wr.get();
}
if (d == null) {
d = getDrawable();
mDrawable = new WeakReference<>(d);
}
return d;
}
}
Mi respuesta ajusta la respuesta misaka-10032. trabajo perfecto!
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;
}
}
Mi respuesta modifica la primera respuesta.
En realidad, he probado los dos métodos anteriores, y no creo que sean realmente de centro vertical.
Haría que el dibujable sea más central si se coloca entre
ascent
y
descent
, en lugar de
top
y
bottom
.
En cuanto a la segunda respuesta, alinea el centro del dibujo con la línea de base del texto, en lugar del centro de ese texto.
Aquí está mi solución:
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;
}
}
También reescribo
getSize
para mantener FontMetrics de dibujable igual que otro texto, de lo contrario, la vista principal no ajustará el contenido correctamente.
Mi versión mejorada: las métricas de fuentes dibujables aumentaron en relación con las métricas de fuentes de texto. Para que el espacio entre líneas se calcule correctamente.
@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;
}
Puede que sea un poco tarde, pero he encontrado una manera de hacerlo, sin importar el tamaño de la imagen.
Debe crear una clase que amplíe ImageSpan y anular los métodos
getSize()
y
getCachedDrawable()
(no necesitamos cambiar el último, pero este método de
DynamicDrawableSpan
es privado y no se puede acceder de otra manera desde la clase secundaria) .
En
getSize(...)
, puede redefinir la forma en que
DynamicDrawableSpan
establece el ascenso / alto / descenso / fondo de la línea y lograr lo que desea hacer.
Aquí está mi ejemplo de clase:
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;
}
¡Avísame si tienes algún problema con esa clase!
ImageSpan una solución de trabajo al crear una clase que hereda de ImageSpan .
Luego se modificó la implementación del sorteo desde DynamicDrawableSpan. Al menos esta implementación funciona cuando la altura de mi imagen es menor que la altura de la fuente. No estoy seguro de cómo funciona esto para imágenes más grandes como la suya.
@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();
}
También tuve que reutilizar la implementación de DynamicDrawableSpan ya que era privada.
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;
Y así es como lo uso como método estático que inserta la imagen delante del texto.
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;
Quizás no sea una buena práctica considerando la localización, pero funciona para mí. Para establecer imágenes en el medio del texto, naturalmente necesitará reemplazar los tokens en el texto con espacios.
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();
}
};