Medición de la altura del texto que se dibujará en Canvas (Android)

¿Alguna manera directa de medir la altura del texto? La forma en que lo estoy haciendo ahora es usando measureText() Paint para obtener el ancho, y luego por prueba y error para encontrar un valor para obtener una altura aproximada. También he estado jugando con FontMetrics , pero todos estos parecen métodos aproximados que apestan.

Estoy tratando de escalar cosas para diferentes resoluciones. Puedo hacerlo, pero termino con un código increíblemente detallado con muchos cálculos para determinar los tamaños relativos. ¡Lo odio! Tiene que haber una mejor manera.

¿Qué pasa con paint.getTextBounds () (método de objeto)

Hay diferentes maneras de medir la altura dependiendo de lo que necesita.

getTextBounds

Si está haciendo algo así como centrar con precisión una pequeña cantidad de texto fijo, es probable que desee getTextBounds . Puedes obtener el rectángulo delimitador como este

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

Como puede ver para las siguientes imágenes, diferentes cadenas le darán diferentes alturas (se muestran en rojo).

enter image description here

Estas alturas diferentes podrían ser una desventaja en algunas situaciones cuando solo se necesita una altura constante sin importar el texto. Ver la siguiente sección.

Paint.FontMetrics

Puede calcular la altura de la fuente a partir de las métricas de fuente. La altura es siempre la misma porque se obtiene de la fuente, no de una cadena de texto en particular.

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

La línea de base es la línea en la que se encuentra el texto. El descenso generalmente es lo más lejos que un personaje irá por debajo de la línea y el ascenso generalmente es lo más lejos que un personaje irá por encima de la línea. Para obtener la altura, debes restar el ascenso porque es un valor negativo. (La línea base es y=0 y descrece en la pantalla).

Mira la siguiente imagen. Las alturas para ambas cadenas son 234.375 .

enter image description here

Si desea la altura de la línea en lugar de la altura del texto, puede hacer lo siguiente:

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

Estas son la bottom y top de la línea. El principal (espaciado entre líneas) es generalmente cero, pero debe agregarlo de todos modos.

Las imágenes de arriba provienen de este proyecto . Puedes jugar con él para ver cómo funciona Font Metrics.

StaticLayout

Para medir la altura del texto de varias líneas, debe usar un StaticLayout . Hablé de ello con cierto detalle en esta respuesta , pero la forma básica de obtener esta altura es la siguiente:

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

La respuesta de @bramp es correcta, en parte porque no menciona que los límites calculados serán el rectángulo mínimo que contiene el texto completamente con las coordenadas de inicio implícitas de 0, 0.

Esto significa que la altura de, por ejemplo, “Py” será diferente de la altura de “py” o “hi” u “oi” o “aw”, ya que en píxeles requieren alturas diferentes.

Esto de ninguna manera es equivalente a FontMetrics en el clásico java.

Si bien el ancho de un texto no es muy doloroso, la altura sí lo es.

En particular, si necesita alinear verticalmente el texto dibujado, intente obtener los límites del texto “a” (sin comillas), en lugar de usar el texto que desea dibujar. Funciona para mi…

Esto es lo que quiero decir:

 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 

Alinear verticalmente el centro del texto significa alinear verticalmente el rectángulo central delimitador, que es diferente para diferentes textos (mayúsculas, letras largas, etc.). Pero lo que realmente queremos hacer es alinear las líneas de base de los textos renderizados, de modo que no parezcan elevados ni estriados. Entonces, mientras sepamos el centro de la letra más pequeña (“a”, por ejemplo), podemos reutilizar su alineación para el rest de los textos. Esto alineará todos los textos al igual que la línea de base y los alineará.

La altura es el tamaño de texto que ha establecido en la variable Paint.

Otra forma de averiguar la altura es

 mPaint.getTextSize(); 

Puede usar la clase android.text.StaticLayout para especificar los límites necesarios y luego llamar a getHeight() . Puede dibujar el texto (contenido en el diseño) llamando a su método de draw(Canvas) .

Simplemente puede obtener el tamaño de texto para un objeto Paint utilizando el método getTextSize (). Por ejemplo:

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

Debe usar Rect.width() y Rect.Height() que devolvieron getTextBounds() lugar. Funciona para mi.

Si alguien todavía tiene problemas, este es mi código.

Tengo una vista personalizada que es cuadrada (ancho = alto) y quiero asignarle un carácter. onDraw() muestra cómo obtener la altura del caracter, aunque no lo estoy usando. El personaje se mostrará en el medio de la vista.

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