web-dev-qa-db-fra.com

Aligner le texte autour du centre ImageSpan vertical

J'ai un ImageSpan à l'intérieur d'un morceau de texte. Ce que j'ai remarqué, c'est que le texte environnant est toujours dessiné au bas de la ligne de texte. Pour être plus précis, la taille de la ligne de texte augmente avec l'image, mais la ligne de base du texte ne se déplace pas vers le haut. Lorsque l’image est visiblement plus grande que la taille du texte, l’effet est plutôt inesthétique. 

Voici un exemple, le contour montre les limites de la TextView: enter image description here

J'essaie de centrer le texte environnant verticalement par rapport à l'image affichée. Voici le même exemple avec du texte bleu indiquant l'emplacement souhaité:

enter image description here

Voici les contraintes auxquelles je suis lié:

  • Je ne peux pas utiliser les drawables composés. Les images doivent pouvoir être affichées entre les mots.
  • Le texte peut être multiligne selon le contenu. Je n'ai aucun contrôle sur ça.
  • Mes images sont plus grandes que le texte environnant et je ne peux pas réduire leur taille. Bien que l'exemple d'image ci-dessus soit plus volumineux que les images réelles (pour illustrer le comportement actuel), les images réelles sont toujours suffisamment grandes pour que ce problème soit perceptible.

J'ai essayé d'utiliser l'attribut Android:gravity="center_vertical" sur TextView, mais cela n'a aucun effet. Je crois que cela centre juste verticalement le texte lignes, mais dans la ligne de texte, le texte est toujours dessiné en bas. 

Mon courant de pensée consiste à créer une étendue personnalisée qui modifie la ligne de base du texte en fonction de la hauteur de la ligne et de la taille du texte actuel. Cette plage engloberait tout le texte et je devrais calculer l'intersection avec les ImageSpans afin d'éviter de déplacer également les images. Cela semble plutôt intimidant et j'espère que quelqu'un pourra suggérer une autre approche.

Toute aide est appréciée!

34
Karakuri

C'est peut-être un peu tard, mais j'ai trouvé un moyen de le faire, peu importe la taille de l'image. Vous devez créer une classe étendant ImageSpan et redéfinir les méthodes getSize() et getCachedDrawable() (nous n'avons pas besoin de changer la dernière, mais cette méthode de DynamicDrawableSpan est privée et ne peut pas être accédée depuis la classe enfant). Dans getSize(...), vous pouvez alors redéfinir la manière dont DynamicDrawableSpan définit la montée/le haut/la descente/le bas de la ligne et réalise ce que vous voulez faire.

Voici mon exemple de classe:

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

Faites-moi savoir si vous avez des problèmes avec cette classe!

26
jujux789

Ma réponse modifie la première réponse. En fait, j'ai essayé les deux méthodes ci-dessus, et je ne pense pas qu'elles soient vraiment verticales. Cela rendrait le centre plus dessinable s'il était placé entre ascent et descent, plutôt que top et bottom. Pour ce qui est de la deuxième réponse, elle aligne le centre du dessin sur la ligne de base du texte, plutôt que le centre de ce texte. Voici ma 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;
  }
}

Je réécris aussi getSize pour que les valeurs FontMetrics de drawable soient identiques à celles des autres textes, sinon la vue parent n'enveloppera pas le contenu correctement.

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

Après avoir lu le code source de TextView, je pense que nous pouvons utiliser la ligne de base de chaque ligne de texte qui est "y" . Et cela fonctionnera même si vous définissez 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();
    }

}
16
xuqingqi

J'ai obtenu une solution de travail en créant une classe qui hérite de ImageSpan .

Ensuite, implémentation de dessin modifiée à partir de DynamicDrawableSpan. Au moins, cette implémentation fonctionne lorsque la hauteur de mon image est inférieure à celle de la police. Vous ne savez pas comment cela fonctionne pour de plus grandes images comme la vôtre.

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

Dû également réutiliser l'implémentation de DynamicDrawableSpan car c'était privé.

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;

Et voici comment je l’utilise comme méthode statique qui insère une image devant le texte.

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;

Peut-être que ce n’est pas une bonne pratique compte tenu de la localisation, mais que cela fonctionne pour moi. Pour définir des images au milieu du texte, vous devez naturellement remplacer les jetons dans le texte par des étendues.

4
ptiili