wake-up-neo.net

Richten Sie den Text um die vertikale Mitte von ImageSpan aus

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: enter image description here

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:

enter image description here

Hier sind die Einschränkungen, an die ich gebunden bin:

  • Ich kann keine zusammengesetzten Drawables verwenden. Die Bilder müssen zwischen Wörtern dargestellt werden können.
  • Der Text kann je nach Inhalt mehrzeilig sein. Ich habe keine Kontrolle darüber.
  • Meine Bilder sind größer als der umgebende Text und ich kann sie nicht verkleinern. Das obige Beispielbild ist zwar größer als die tatsächlichen Bilder (um das aktuelle Verhalten zu veranschaulichen), die tatsächlichen Bilder sind jedoch noch groß genug, um dieses Problem zu bemerken.

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 ImageSpans 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!

34
Karakuri

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!

26
jujux789

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.

55
misaka-10032
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();
                }
            };
21
boiledwater

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

}
16
xuqingqi

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.

4
ptiili

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;
    }
}
0
heng li