Ich habe eine ImageSpan
innerhalb eines Textstücks. Was mir aufgefallen ist, ist, dass der umgebende Text immer am unteren Rand der Textzeile gezeichnet wird - genauer gesagt, die Größe der Textzeile wächst mit dem Bild, aber die Grundlinie des Textes verschiebt sich nicht nach oben. Wenn das Bild deutlich größer als die Textgröße ist, ist der Effekt eher unansehnlich.
Hier ist ein Beispiel, die Umrisse zeigen Grenzen der TextView
:
Ich versuche, den umgebenden Text in Bezug auf das angezeigte Bild vertikal zentrieren zu lassen. Hier ist das gleiche Beispiel mit blauem Text, der die gewünschte Position zeigt:
Hier sind die Einschränkungen, an die ich gebunden bin:
Ich habe versucht, das Android:gravity="center_vertical"
-Attribut in der TextView zu verwenden, dies hat jedoch keine Auswirkungen. Ich glaube, das zentriert den Text lines nur vertikal, aber innerhalb der Textzeile wird der Text immer noch am unteren Rand gezeichnet.
Mein derzeitiger Gedankengang ist das Erstellen eines benutzerdefinierten Bereichs, der die Grundlinie des Texts basierend auf der Höhe der Linie und der aktuellen Textgröße verschiebt. Diese Spanne würde den gesamten Text umfassen, und ich müsste die Schnittmenge mit der Variablen ImageSpan
s berechnen, damit ich die Bilder auch nicht verschieben kann. Das klingt ziemlich entmutigend und ich hoffe, dass jemand einen anderen Ansatz vorschlagen kann.
Jede Hilfe wird geschätzt!
Es könnte etwas spät sein, aber ich habe einen Weg gefunden, dies unabhängig von der Bildgröße. Sie müssen eine Klasse erstellen, die ImageSpan erweitert, und die Methoden getSize()
und getCachedDrawable()
überschreiben (wir müssen die letzte nicht ändern, aber diese Methode von DynamicDrawableSpan
ist privat und kann nicht auf andere Weise von der untergeordneten Klasse aus aufgerufen werden). In getSize(...)
können Sie dann die Art und Weise neu definieren, wie DynamicDrawableSpan
den Aufstieg, den oberen, den absteigenden und den unteren Teil der Zeile festgelegt hat, um das zu erreichen, was Sie tun möchten.
Hier ist mein Klassenbeispiel:
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;
}
Lassen Sie mich wissen, wenn Sie Probleme mit dieser Klasse haben!
Meine Antwort verändert die erste Antwort. Eigentlich habe ich oben beide Methoden ausprobiert, und ich glaube nicht, dass sie wirklich in der Mitte vertikal sind. Es würde den Zeichnungspunkt mehr zentrieren, wenn er zwischen ascent
und descent
statt top
und bottom
platziert wird. In Bezug auf die zweite Antwort wird der Mittelpunkt des Zeichenbaren an der Grundlinie des Textes und nicht am Mittelpunkt dieses Textes ausgerichtet. Hier ist meine Lösung:
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;
}
}
Ich schreibe auch getSize
um, damit FontMetrics von drawable genauso bleibt wie der andere Text. Andernfalls wird die übergeordnete Ansicht den Inhalt nicht korrekt umbrechen.
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();
}
};
Nach dem Lesen des Quellcodes von TextView, denke ich, können wir die baseLine der eache-Textzeile verwenden, die "y" ..__ ist. Und sie funktioniert auch, wenn Sie lineSpaceExtra setzen.
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();
}
}
Ich habe eine funktionierende Lösung erhalten, indem ich eine Klasse erstellt habe, die von ImageSpan erbt.
Anschließend wurde die Zeichnungsimplementierung von DynamicDrawableSpan geändert. Zumindest funktioniert diese Implementierung, wenn meine Bildhöhe geringer als die Schrifthöhe ist. Nicht sicher, wie das für größere Bilder wie Ihre funktioniert.
@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();
}
Außerdem musste die Implementierung von DynamicDrawableSpan wieder verwendet werden, da sie privat war.
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;
Und so benutze ich es als statische Methode, die ein Bild vor den Text einfügt.
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;
Es ist vielleicht keine gute Praxis, wenn es um die Lokalisierung geht, aber es funktioniert für mich. Um Bilder in die Mitte des Texts zu setzen, müssen Sie natürlich Textmarken durch Spannweiten ersetzen.
Meine Antwort ändert die Misaka-10032-Antwort. Arbeit perfekt!
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;
}
}