Android De Peinture:.measureText () vs. getTextBounds()
je mesure le texte en utilisant Paint.getTextBounds()
, car je suis intéressé à obtenir à la fois la hauteur et la largeur du texte à rendre. Cependant, le texte réel rendu est toujours un peu plus large que le .width()
du Rect
information remplie par getTextBounds()
.
à ma grande surprise, j'ai testé .measureText()
, et j'ai trouvé qu'il renvoie une valeur différente (plus élevée). J'ai essayé, et j'ai trouvé ça correct.
pourquoi déclarent-ils différents largeurs? Comment puis-je obtenir correctement la hauteur et la largeur? Je veux dire , je peut utiliser .measureText()
, mais alors je ne saurais pas si je devrais faire confiance au .height()
retourné par getTextBounds()
.
comme demandé, voici le code minimal pour reproduire le problème:
final String someText = "Hello. I believe I'm some text!";
Paint p = new Paint();
Rect bounds = new Rect();
for (float f = 10; f < 40; f += 1f) {
p.setTextSize(f);
p.getTextBounds(someText, 0, someText.length(), bounds);
Log.d("Test", String.format(
"Size %f, measureText %f, getTextBounds %d",
f,
p.measureText(someText),
bounds.width())
);
}
la sortie montre que la différence non seulement obtient plus de 1 (et n'est pas une erreur d'arrondi de dernière minute), mais semble également augmenter avec la taille (j'étais sur le point de tirer plus de conclusions, mais il peut être entièrement fonction de la police):
D/Test ( 607): Size 10.000000, measureText 135.000000, getTextBounds 134
D/Test ( 607): Size 11.000000, measureText 149.000000, getTextBounds 148
D/Test ( 607): Size 12.000000, measureText 156.000000, getTextBounds 155
D/Test ( 607): Size 13.000000, measureText 171.000000, getTextBounds 169
D/Test ( 607): Size 14.000000, measureText 195.000000, getTextBounds 193
D/Test ( 607): Size 15.000000, measureText 201.000000, getTextBounds 199
D/Test ( 607): Size 16.000000, measureText 211.000000, getTextBounds 210
D/Test ( 607): Size 17.000000, measureText 225.000000, getTextBounds 223
D/Test ( 607): Size 18.000000, measureText 245.000000, getTextBounds 243
D/Test ( 607): Size 19.000000, measureText 251.000000, getTextBounds 249
D/Test ( 607): Size 20.000000, measureText 269.000000, getTextBounds 267
D/Test ( 607): Size 21.000000, measureText 275.000000, getTextBounds 272
D/Test ( 607): Size 22.000000, measureText 297.000000, getTextBounds 294
D/Test ( 607): Size 23.000000, measureText 305.000000, getTextBounds 302
D/Test ( 607): Size 24.000000, measureText 319.000000, getTextBounds 316
D/Test ( 607): Size 25.000000, measureText 330.000000, getTextBounds 326
D/Test ( 607): Size 26.000000, measureText 349.000000, getTextBounds 346
D/Test ( 607): Size 27.000000, measureText 357.000000, getTextBounds 354
D/Test ( 607): Size 28.000000, measureText 369.000000, getTextBounds 365
D/Test ( 607): Size 29.000000, measureText 396.000000, getTextBounds 392
D/Test ( 607): Size 30.000000, measureText 401.000000, getTextBounds 397
D/Test ( 607): Size 31.000000, measureText 418.000000, getTextBounds 414
D/Test ( 607): Size 32.000000, measureText 423.000000, getTextBounds 418
D/Test ( 607): Size 33.000000, measureText 446.000000, getTextBounds 441
D/Test ( 607): Size 34.000000, measureText 455.000000, getTextBounds 450
D/Test ( 607): Size 35.000000, measureText 468.000000, getTextBounds 463
D/Test ( 607): Size 36.000000, measureText 474.000000, getTextBounds 469
D/Test ( 607): Size 37.000000, measureText 500.000000, getTextBounds 495
D/Test ( 607): Size 38.000000, measureText 506.000000, getTextBounds 501
D/Test ( 607): Size 39.000000, measureText 521.000000, getTextBounds 515
7 réponses
vous pouvez faire ce que j'ai fait pour inspecter un tel problème:
Étude du code source Android, de la Peinture.java source, voir les méthodes measureText et getTextBounds. Vous apprendriez que measureText appelle native_measureText, et gettextextbounds appelle nativeGetStringBounds, qui sont des méthodes natives implémentées en C++.
pour continuer à étudier la peinture.rpc, qui implémente à la fois.
native_measureText - > SkPaintGlue:: measureText_CII
nativeGetStringBounds - > SkPaintGlue:: getStringBounds
Maintenant, votre étude vérifie où ces méthodes diffèrent. Après quelques vérifications param, les deux fonctions D'appel SkPaint:: measureText dans Skia Lib (partie D'Android), mais ils appellent tous les deux différente forme surchargée.
en creusant plus loin dans Skia, je vois que les deux appels aboutissent au même calcul dans la même fonction, seul le résultat de retour est différent.
à répondez à votre question: Vos deux appels font le même calcul. La différence Possible du résultat réside en fait que getTextBounds renvoie des limites comme entier, tandis que measurtext renvoie une valeur flottante.
donc ce que vous obtenez est une erreur d'arrondi lors de la conversion de flotteur en int, et cela se produit dans la peinture.rpc dans SkPaintGlue::doTextBounds dans l'appel à la fonction SkRect::roundOut.
la différence entre la largeur calculée de ces deux appels peut être au maximum de 1.
EDIT 4 Oct 2011
Ce qui peut être mieux que la visualisation. J'ai pris l'effort, afin de les explorer, et pour méritants bounty :)
c'est la taille de police 60, en rouge est limites rectangle, en Pourpre est le résultat de measureText.
c'est vu cette partie limite gauche commence quelques pixels à partir de la gauche, et la valeur de measureText est incrémentée par cette valeur à la fois à gauche et à droite. C'est ce qu'on appelle la valeur AdvanceX de Glyph. (J'ai découvert ceci dans Skia sources in SkPaint.cpp)
ainsi le résultat du test est que measureText ajoute une certaine valeur d'avance au texte des deux côtés, tandis que getTextBounds calcule des limites minimales où le texte donné conviendra.
J'espère que ce résultat vous sera utile.
code D'essai:
protected void onDraw(Canvas canvas){
final String s = "Hello. I'm some text!";
Paint p = new Paint();
Rect bounds = new Rect();
p.setTextSize(60);
p.getTextBounds(s, 0, s.length(), bounds);
float mt = p.measureText(s);
int bw = bounds.width();
Log.i("LCG", String.format(
"measureText %f, getTextBounds %d (%s)",
mt,
bw, bounds.toShortString())
);
bounds.offset(0, -bounds.top);
p.setStyle(Style.STROKE);
canvas.drawColor(0xff000080);
p.setColor(0xffff0000);
canvas.drawRect(bounds, p);
p.setColor(0xff00ff00);
canvas.drawText(s, 0, bounds.bottom, p);
}
mon expérience avec ceci est que getTextBounds
retournera cette limite minimale absolue rect qui encapsule le texte, pas nécessairement la largeur mesurée utilisée lors du rendu. Je veux aussi dire que measureText
suppose une ligne.
pour obtenir des résultats de mesure précis, vous devez utiliser le StaticLayout
pour rendre le texte et extraire les mesures.
par exemple:
String text = "text";
TextPaint textPaint = textView.getPaint();
int boundedWidth = 1000;
StaticLayout layout = new StaticLayout(text, textPaint, boundedWidth , Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
int height = layout.getHeight();
La souris réponse est grande... Et voici la description du vrai problème:
la réponse courte et simple est que Paint.getTextBounds(String text, int start, int end, Rect bounds)
retourne Rect
qui ne commence pas à (0,0)
. C'est-à-dire, pour obtenir la largeur réelle du texte qui sera défini en appelant Canvas.drawText(String text, float x, float y, Paint paint)
avec le même objet de peinture de getTextBounds() vous devez ajouter la position gauche de Rect. Quelque chose comme ça:
public int getTextWidth(String text, Paint paint) {
Rect bounds = new Rect();
paint.getTextBounds(text, 0, end, bounds);
int width = bounds.left + bounds.width();
return width;
}
notez ceci bounds.left
- ce la clé du problème.
De cette façon, vous recevrez la même largeur de texte, que vous recevrez à l'aide de Canvas.drawText()
.
et la même fonction devrait être pour obtenir height
du texte:
public int getTextHeight(String text, Paint paint) {
Rect bounds = new Rect();
paint.getTextBounds(text, 0, end, bounds);
int height = bounds.bottom + bounds.height();
return height;
}
P. S.: Je n'ai pas testé ce code exact, mais j'ai testé la conception.
une explication beaucoup plus détaillée est donnée dans réponse.
Désolé d'avoir à nouveau répondu à cette question... J'avais besoin d'intégrer l'image.
je pense que les résultats @souris trouvées sont missleading. Les observations peuvent être correctes pour la taille de police de 60, mais elles deviennent beaucoup plus différentes lorsque le texte est plus petit. Par exemple. 10px. Dans ce cas, le texte est en fait tiré au-delà des limites.
code source de la capture d'écran:
@Override
protected void onDraw( Canvas canvas ) {
for( int i = 0; i < 20; i++ ) {
int startSize = 10;
int curSize = i + startSize;
paint.setTextSize( curSize );
String text = i + startSize + " - " + TEXT_SNIPPET;
Rect bounds = new Rect();
paint.getTextBounds( text, 0, text.length(), bounds );
float top = STEP_DISTANCE * i + curSize;
bounds.top += top;
bounds.bottom += top;
canvas.drawRect( bounds, bgPaint );
canvas.drawText( text, 0, STEP_DISTANCE * i + curSize, paint );
}
}
AVERTISSEMENT: Cette solution n'est pas précis à 100% en termes de détermination de la largeur minimale.
j'étais aussi en train de comprendre comment mesurer le texte sur une toile. Après avoir lu le Grand post de souris j'ai eu quelques problèmes sur la façon de mesurer le texte multiligne. Il n'y a pas de façon évidente de ces contributions, mais après quelques recherches, je suis passé à travers la classe de Staclayout. Il vous permet de mesurer le texte multiligne (texte avec "\n") et configurer beaucoup plus propriétés de votre texte via la peinture associée.
voici un extrait montrant comment mesurer le texte multiligne:
private StaticLayout measure( TextPaint textPaint, String text, Integer wrapWidth ) {
int boundedWidth = Integer.MAX_VALUE;
if (wrapWidth != null && wrapWidth > 0 ) {
boundedWidth = wrapWidth;
}
StaticLayout layout = new StaticLayout( text, textPaint, boundedWidth, Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false );
return layout;
}
le wrapwitdh est capable de déterminer si vous voulez limiter votre texte multiligne à une certaine largeur.
depuis le StaticLayout.getWidth () retourne seulement ce boundedWidth vous devez faire une autre étape pour obtenir la largeur maximale requise par votre texte multiligne. Vous êtes en mesure de déterminer la largeur de chaque ligne et la largeur maximale est la largeur de ligne la plus élevée bien sûr:
private float getMaxLineWidth( StaticLayout layout ) {
float maxLine = 0.0f;
int lineCount = layout.getLineCount();
for( int i = 0; i < lineCount; i++ ) {
if( layout.getLineWidth( 0 ) > maxLine ) {
maxLine = layout.getLineWidth( 0 );
}
}
return maxLine;
}
il y a une autre façon de mesurer les limites du texte avec précision, vous devez d'abord obtenir le chemin pour la peinture et le texte actuels. Dans votre cas, il devrait être comme ceci:
p.getTextPath(someText, 0, someText.length(), 0.0f, 0.0f, mPath);
ensuite vous pouvez appeler:
mPath.computeBounds(mBoundsPath, true);
dans mon code, il renvoie toujours les valeurs correctes et attendues. Mais, pas sûr si cela fonctionne plus vite que votre approche.
C'est ainsi que j'ai calculé les dimensions réelles de la première lettre (vous pouvez changer l'en-tête de la méthode pour répondre à vos besoins, i.e. au lieu de char[]
utiliser String
):
private void calculateTextSize(char[] text, PointF outSize) {
// use measureText to calculate width
float width = mPaint.measureText(text, 0, 1);
// use height from getTextBounds()
Rect textBounds = new Rect();
mPaint.getTextBounds(text, 0, 1, textBounds);
float height = textBounds.height();
outSize.x = width;
outSize.y = height;
}
notez que J'utilise TextPaint au lieu de la classe de peinture originale.