Mesurer la hauteur du texte à dessiner sur Canvas (Android)

132

Un moyen simple de mesurer la hauteur du texte? La façon dont je le fais maintenant est d'utiliser Paint's measureText()pour obtenir la largeur, puis par essais et erreurs, trouver une valeur pour obtenir une hauteur approximative. J'ai aussi joué avec FontMetrics, mais tout cela semble être des méthodes approximatives qui craignent.

J'essaie de mettre les choses à l'échelle pour différentes résolutions. Je peux le faire, mais je me retrouve avec un code incroyablement détaillé avec beaucoup de calculs pour déterminer les tailles relatives. Je déteste ça! Il doit y avoir une meilleure façon.

Danedo
la source

Réponses:

136

Qu'en est-il de paint.getTextBounds () (méthode objet)

bramp
la source
1
Cela donne des résultats très étranges, lorsque j'évalue la hauteur d'un texte. Un texte court donne une hauteur de 12, alors qu'un texte VRAIMENT long donne une hauteur de 16 (étant donné une taille de police de 16). Cela n'a aucun sens pour moi (Android 2.3.3)
AgentKnopf
36
La variance de hauteur correspond à l'endroit où vous avez des descendants dans le texte, c'est-à-dire que «High» est plus grand que «Low» en raison de la partie du g sous la ligne
FrinkTheBrave
208

Il existe différentes façons de mesurer la hauteur en fonction de ce dont vous avez besoin.

getTextBounds

Si vous faites quelque chose comme centrer précisément une petite quantité de texte fixe, vous le souhaitez probablement getTextBounds. Vous pouvez obtenir le rectangle de délimitation comme ceci

Rect bounds = new Rect();
mTextPaint.getTextBounds(mText, 0, mText.length(), bounds);
int height = bounds.height();

Comme vous pouvez le voir pour les images suivantes, différentes chaînes donneront des hauteurs différentes (indiquées en rouge).

entrez la description de l'image ici

Ces différentes hauteurs peuvent être un inconvénient dans certaines situations où vous avez juste besoin d'une hauteur constante, quel que soit le texte. Voir la section suivante.

Paint.FontMetrics

Vous pouvez calculer la hauteur de la police à partir des métriques de police. La hauteur est toujours la même car elle est obtenue à partir de la police et non d'une chaîne de texte particulière.

Paint.FontMetrics fm = mTextPaint.getFontMetrics();
float height = fm.descent - fm.ascent;

La ligne de base est la ligne sur laquelle se trouve le texte. La descente est généralement la plus éloignée qu'un personnage ira en dessous de la ligne et l'ascension est généralement la plus éloignée qu'un personnage ira au-dessus de la ligne. Pour obtenir la hauteur, vous devez soustraire l'ascension car c'est une valeur négative. (La ligne de base est y=0et ydescend vers le haut de l'écran.)

Regardez l'image suivante. Les hauteurs des deux cordes sont 234.375.

entrez la description de l'image ici

Si vous voulez la hauteur de ligne plutôt que simplement la hauteur du texte, vous pouvez procéder comme suit:

float height = fm.bottom - fm.top + fm.leading; // 265.4297

Ce sont les bottomet topde la ligne. L'interligne (espacement interligne) est généralement égal à zéro, mais vous devez quand même l'ajouter.

Les images ci-dessus proviennent de ce projet . Vous pouvez jouer avec pour voir comment fonctionnent les mesures de police.

StaticLayout

Pour mesurer la hauteur d'un texte multiligne, vous devez utiliser un StaticLayout. J'en ai parlé en détail dans cette réponse , mais le moyen de base pour obtenir cette hauteur est comme suit:

String text = "This is some text. This is some text. This is some text. This is some text. This is some text. This is some text.";

TextPaint myTextPaint = new TextPaint();
myTextPaint.setAntiAlias(true);
myTextPaint.setTextSize(16 * getResources().getDisplayMetrics().density);
myTextPaint.setColor(0xFF000000);

int width = 200;
Layout.Alignment alignment = Layout.Alignment.ALIGN_NORMAL;
float spacingMultiplier = 1;
float spacingAddition = 0;
boolean includePadding = false;

StaticLayout myStaticLayout = new StaticLayout(text, myTextPaint, width, alignment, spacingMultiplier, spacingAddition, includePadding);

float height = myStaticLayout.getHeight(); 
Suragch
la source
Belle explication. De quelle application proviennent les captures d'écran?
Micheal Johnson
1
@MichealJohnson, j'ai ajouté l'application en tant que projet GitHub ici .
Suragch
1
Qu'est-ce que "getTextSize" vous donne alors?
développeur android
1
Paint's getTextSize()vous donne la taille de la police en unités de pixels (par opposition aux spunités). @androiddeveloper
Suragch
2
Quelle est la relation entre la taille de la police en pixels et la hauteur mesurée et les dimensions FontMetrics ? C'est une question que j'aimerais approfondir davantage.
Suragch
85

La réponse de @ bramp est correcte - partiellement, en ce sens qu'elle ne mentionne pas que les limites calculées seront le rectangle minimum qui contient le texte entièrement avec les coordonnées de début implicites de 0, 0.

Cela signifie que la hauteur de, par exemple "Py" sera différente de la hauteur de "py" ou "hi" ou "oi" ou "aw" parce que, au niveau des pixels, ils nécessitent des hauteurs différentes.

Ce n'est en aucun cas un équivalent à FontMetrics dans Java classique.

Alors que la largeur d'un texte n'est pas très pénible, la hauteur l'est.

En particulier, si vous devez aligner verticalement le texte dessiné au centre, essayez d'obtenir les limites du texte "a" (sans guillemets), au lieu d'utiliser le texte que vous souhaitez dessiner. Travaille pour moi...

Voici ce que je veux dire:

Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.LINEAR_TEXT_FLAG);

paint.setStyle(Paint.Style.FILL);
paint.setColor(color);
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(textSize);

Rect bounds = new Rect();
paint.getTextBounds("a", 0, 1, bounds);

buffer.drawText(this.myText, canvasWidth >> 1, (canvasHeight + bounds.height()) >> 1, paint);
// remember x >> 1 is equivalent to x / 2, but works much much faster

Centrer verticalement le texte signifie aligner verticalement le rectangle de délimitation - ce qui est différent pour différents textes (majuscules, lettres longues, etc.). Mais ce que nous voulons en fait, c'est également aligner les lignes de base des textes rendus, de sorte qu'ils n'apparaissent pas surélevés ou rainurés. Ainsi, tant que nous connaissons le centre de la plus petite lettre ("a" par exemple), nous pouvons alors réutiliser son alignement pour le reste des textes. Cela permettra d'aligner tous les textes au centre et de les aligner sur la ligne de base.

Nar Gar
la source
25
Je n'ai pas vu x >> 1depuis des lustres. Upvoting only for that :)
keaukraine
62
Un bon compilateur moderne le verra x / 2et l'optimisera pourx >> 1
intrépidis
37
@keaukraine x / 2est beaucoup plus convivial lors de la lecture de code, compte tenu du commentaire de Chris.
d3dave
qu'est-ce que buffercet exemple? Est-ce le canvaspassé à la draw(Canvas)méthode?
AutonomousApps
@AutonomousApps oui, c'est la toile
Chisko
16

La hauteur correspond à la taille du texte que vous avez définie sur la variable Paint.

Une autre façon de connaître la hauteur est

mPaint.getTextSize();
Kimnod
la source
3

Vous pouvez utiliser la android.text.StaticLayoutclasse pour spécifier les limites requises, puis appeler getHeight(). Vous pouvez dessiner le texte (contenu dans la mise en page) en appelant sa draw(Canvas)méthode.

intrépidis
la source
2

Vous pouvez simplement obtenir la taille du texte d'un objet Paint en utilisant la méthode getTextSize (). Par exemple:

Paint mTextPaint = new Paint (Paint.ANTI_ALIAS_FLAG);
//use densityMultiplier to take into account different pixel densities
final float densityMultiplier = getContext().getResources()
            .getDisplayMetrics().density;  
mTextPaint.setTextSize(24.0f*densityMultiplier);

//...

float size = mTextPaint.getTextSize();
moondroid
la source
D'où vient le 24.0f?
Erigami
24.0f c'est juste un exemple de taille de texte
moondroid
1

Vous devez utiliser Rect.width()et Rect.Height()qui est revenu à la getTextBounds()place. Ça marche pour moi.

Putti
la source
2
Pas si vous avez affaire à plusieurs segments de texte. La raison est dans ma réponse ci-dessus.
Nar Gar
0

Si quelqu'un a encore des problèmes, c'est mon code.

J'ai une vue personnalisée carrée (largeur = hauteur) et je souhaite lui attribuer un caractère. onDraw()montre comment obtenir une hauteur de caractère, même si je ne l'utilise pas. Le personnage sera affiché au milieu de la vue.

public class SideBarPointer extends View {

    private static final String TAG = "SideBarPointer";

    private Context context;
    private String label = "";
    private int width;
    private int height;

    public SideBarPointer(Context context) {
        super(context);
        this.context = context;
        init();
    }

    public SideBarPointer(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        init();
    }

    public SideBarPointer(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.context = context;
        init();
    }

    private void init() {
//        setBackgroundColor(0x64FF0000);
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        height = this.getMeasuredHeight();
        width = this.getMeasuredWidth();

        setMeasuredDimension(width, width);
    }

    protected void onDraw(Canvas canvas) {
        float mDensity = context.getResources().getDisplayMetrics().density;
        float mScaledDensity = context.getResources().getDisplayMetrics().scaledDensity;

        Paint previewPaint = new Paint();
        previewPaint.setColor(0x0C2727);
        previewPaint.setAlpha(200);
        previewPaint.setAntiAlias(true);

        Paint previewTextPaint = new Paint();
        previewTextPaint.setColor(Color.WHITE);
        previewTextPaint.setAntiAlias(true);
        previewTextPaint.setTextSize(90 * mScaledDensity);
        previewTextPaint.setShadowLayer(5, 1, 2, Color.argb(255, 87, 87, 87));

        float previewTextWidth = previewTextPaint.measureText(label);
//        float previewTextHeight = previewTextPaint.descent() - previewTextPaint.ascent();
        RectF previewRect = new RectF(0, 0, width, width);

        canvas.drawRoundRect(previewRect, 5 * mDensity, 5 * mDensity, previewPaint);
        canvas.drawText(label, (width - previewTextWidth)/2, previewRect.top - previewTextPaint.ascent(), previewTextPaint);

        super.onDraw(canvas);
    }

    public void setLabel(String label) {
        this.label = label;
        Log.e(TAG, "Label: " + label);

        this.invalidate();
    }
}
Hesam
la source
3
-1 pour les allocations dans onDraw (): Cela donnerait une tonne d'avantages en termes de performances si vous deviez déclarer les champs dans la classe (initier dans le constructeur) et les réutiliser dans onDraw ().
zoltish