linearlayout - textview android
Android Paint:.measureText() frente a.getTextBounds() (7)
Así es como calculé las dimensiones reales para la primera letra (puede cambiar el encabezado del método para que se ajuste a sus necesidades, es decir, en lugar de usar la String
char[]
):
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;
}
Tenga en cuenta que estoy usando TextPaint en lugar de la clase original de Paint.
Estoy midiendo texto usando Paint.getTextBounds()
, ya que estoy interesado en obtener el alto y el ancho del texto para representar. Sin embargo, el texto real procesado siempre es un poco más ancho que el .width()
de la información de Rect
llena por getTextBounds()
.
Para mi sorpresa, probé .measureText()
, y encontré que devuelve un valor diferente (más alto). Lo intenté y lo encontré correcto.
¿Por qué informan diferentes anchuras? ¿Cómo puedo obtener correctamente el alto y el ancho? Quiero decir, puedo usar .measureText()
, pero entonces no sabría si debería confiar en .height()
devuelto por getTextBounds()
.
Según lo solicitado, aquí hay un código mínimo para reproducir el problema:
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())
);
}
El resultado muestra que la diferencia no solo es mayor que 1 (y no es un error de redondeo de último minuto), sino que también parece aumentar con el tamaño (estaba a punto de sacar más conclusiones, pero puede depender enteramente de la fuente):
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
Hay otra manera de medir los límites de texto con precisión, primero debe obtener la ruta para la pintura y el texto actuales. En tu caso, debería ser así:
p.getTextPath(someText, 0, someText.length(), 0.0f, 0.0f, mPath);
Después de eso puedes llamar:
mPath.computeBounds(mBoundsPath, true);
En mi código, siempre devuelve los valores correctos y esperados. Pero no estoy seguro de si funciona más rápido que tu enfoque.
La respuesta de los ratones es genial ... Y aquí está la descripción del problema real:
La respuesta corta y simple es que Paint.getTextBounds(String text, int start, int end, Rect bounds)
devuelve Rect
que no comienza en (0,0)
. Es decir, para obtener el ancho real del texto que se establecerá al llamar a Canvas.drawText(String text, float x, float y, Paint paint)
con el mismo objeto Paint de getTextBounds (), debe agregar la posición izquierda de Rect. Algo como eso:
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;
}
Observe esto bounds.left
- esta es la clave del problema.
De esta forma, recibirá el mismo ancho de texto que recibiría usando Canvas.drawText()
.
Y la misma función debería ser para obtener la height
del texto:
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;
}
PD: No probé este código exacto, pero probé la concepción.
Se da una explicación mucho más detallada en this respuesta.
Mi experiencia con esto es que getTextBounds
devolverá ese rect límite mínimo absoluto que encapsula el texto, no necesariamente el ancho medido usado al renderizar. También quiero decir que measureText
asume una línea.
Para obtener resultados de medición precisos, debe usar StaticLayout
para representar el texto y extraer las medidas.
Por ejemplo:
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();
Perdón por responder nuevamente sobre esa pregunta ... Necesitaba incrustar la imagen.
Creo que los resultados @mice encontrados son engañosos. Las observaciones pueden ser correctas para el tamaño de fuente de 60, pero se vuelven mucho más diferentes cuando el texto es más pequeño. P.ej. 10px. En ese caso, el texto en realidad se dibuja MÁS ALLÁ de los límites.
Código fuente de la captura de pantalla:
@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 );
}
}
Puedes hacer lo que hice para inspeccionar ese problema:
Estudie el código fuente de Android, fuente Paint.java, vea los métodos measureText y getTextBounds. Aprenderá que measureText llama a native_measureText, y getTextBounds llama a nativeGetStringBounds, que son métodos nativos implementados en C ++.
Entonces seguirías estudiando Paint.cpp, que implementa ambos.
native_measureText -> SkPaintGlue :: measureText_CII
nativeGetStringBounds -> SkPaintGlue :: getStringBounds
Ahora su estudio verifica dónde difieren estos métodos. Después de algunos controles de param, ambos llaman a la función SkPaint :: measureText en Skia Lib (parte de Android), pero ambos llaman a diferentes formas sobrecargadas.
Profundizando en Skia, veo que ambas llamadas resultan en el mismo cálculo en la misma función, solo devuelven el resultado de manera diferente.
Para responder a su pregunta: sus dos llamadas hacen el mismo cálculo. La posible diferencia de resultado radica en que getTextBounds devuelve los límites como un entero, mientras que measureText devuelve el valor de flotación.
Entonces lo que obtienes es un error de redondeo durante la conversión de float a int, y esto sucede en Paint.cpp en SkPaintGlue :: doTextBounds en la llamada a la función SkRect :: roundOut.
La diferencia entre el ancho calculado de esas dos llamadas puede ser como máximo 1.
EDITAR 4 oct 2011
Qué puede ser mejor que la visualización. Me tomé el esfuerzo, para explorar y merecer la recompensa :)
Este es el tamaño de fuente 60, en rojo es el rectángulo de límites , en morado es el resultado de measureText.
Se ha visto que los límites de la parte izquierda inician algunos píxeles desde la izquierda, y el valor de measureText se incrementa en este valor tanto a la izquierda como a la derecha. Esto es algo llamado valor de AdvanceX de Glyph. (Lo he descubierto en fuentes de Skia en SkPaint.cpp)
Entonces, el resultado de la prueba es que measureText agrega un cierto valor de avance al texto en ambos lados, mientras que getTextBounds calcula los límites mínimos donde el texto dado se ajustará.
Espero que este resultado te sea útil.
Código de prueba:
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);
}
DESCARGO DE RESPONSABILIDAD: esta solución no es 100% precisa en términos de determinar el ancho mínimo.
También estaba descifrando cómo medir el texto en un lienzo. Después de leer la gran publicación de ratones tuve algunos problemas sobre cómo medir el texto de varias líneas. No hay una forma obvia de estas contribuciones, pero después de un poco de investigación atravieso la clase StaticLayout. Le permite medir texto de líneas múltiples (texto con "/ n") y configurar muchas más propiedades de su texto a través de la pintura asociada.
Aquí hay un fragmento que muestra cómo medir el texto de varias líneas:
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;
}
El wrapwitdh puede determinar si desea limitar su texto de línea múltiple a un ancho determinado.
Como StaticLayout.getWidth () solo devuelve este boundedWidth, debe dar otro paso para obtener el ancho máximo requerido por su texto multilínea. Puede determinar el ancho de cada línea y el ancho máximo es el ancho de línea más alto por supuesto:
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;
}