Medición de la altura del texto para dibujar en Canvas (Android)

132

¿Alguna forma directa de medir la altura del texto? La forma en que lo estoy haciendo ahora es usando Paint measureText()para obtener el ancho, luego, por prueba y error, encontrar un valor para obtener una altura aproximada. También he estado jugando 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.

Danedo
fuente

Respuestas:

136

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

bramp
fuente
1
Esto produce resultados muy extraños cuando evalúo la altura de un texto. Un texto corto da como resultado una altura de 12, mientras que un texto REALMENTE largo da como resultado una altura de 16 (dado un tamaño de fuente de 16). No tiene sentido para mí (android 2.3.3)
AgentKnopf
35
La variación en la altura es donde tiene descendientes en el texto, es decir, 'Alto' es más alto que 'Bajo' debido a la parte de la g debajo de la línea
FrinkTheBrave
208

Existen diferentes formas de medir la altura según lo que necesite.

getTextBounds

Si está haciendo algo como centrar con precisión una pequeña cantidad de texto fijo, probablemente lo 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 en las siguientes imágenes, diferentes cadenas darán diferentes alturas (se muestran en rojo).

ingrese la descripción de la imagen aquí

Estas alturas diferentes podrían ser una desventaja en algunas situaciones en las que solo 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 la 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 es generalmente lo más lejos que un personaje irá debajo de la línea y el ascenso es generalmente lo más lejos que un personaje irá por encima de la línea. Para obtener la altura hay que restar el ascenso porque es un valor negativo. (La línea base es y=0y ydisminuye la pantalla).

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

ingrese la descripción de la imagen aquí

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

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

Estos son los bottomy topde la línea. El inicio (espacio interlineal) suele ser cero, pero debe agregarlo de todos modos.

Las imágenes de arriba provienen de este proyecto . Puedes jugar con él para ver cómo funcionan las métricas de fuentes.

StaticLayout

Para medir la altura del texto de varias líneas, debe usar a StaticLayout. Hablé sobre 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(); 
Suragch
fuente
Buena explicación ¿De qué aplicación son las capturas de pantalla?
Micheal Johnson
1
@MichealJohnson, agregué la aplicación como un proyecto de GitHub aquí .
Suragch
1
Entonces, ¿qué te da "getTextSize"?
desarrollador de Android
1
Paint le getTextSize()da el tamaño de fuente en unidades de píxeles (en oposición a las spunidades). @androiddeveloper
Suragch
2
¿Cuál es la relación del tamaño de fuente en unidades de píxeles con la altura medida y con las dimensiones de FontMetrics ? Esa es una pregunta que me gustaría explorar más.
Suragch
85

La respuesta de @ bramp es correcta, en parte, ya que no menciona que los límites calculados serán el rectángulo mínimo que contiene el texto completamente con 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" porque en cuanto a píxeles requieren diferentes alturas.

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

Mientras que el ancho de un texto no es un gran problema, la altura sí lo es.

En particular, si necesita alinear verticalmente al centro del texto dibujado, intente obtener los límites del texto "a" (sin comillas), en lugar de utilizar 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 al centro del texto significa alinear verticalmente al centro del rectángulo delimitador, que es diferente para diferentes textos (mayúsculas, letras largas, etc.). Pero lo que realmente queremos hacer es alinear también las líneas de base de los textos renderizados, de modo que no parezcan elevados o acanalados. Por lo tanto, siempre que conozcamos el centro de la letra más pequeña ("a", por ejemplo), podemos reutilizar su alineación para el resto de los textos. Esto centrará la alineación de todos los textos, así como la alineación de referencia.

Nar Gar
fuente
25
No he visto x >> 1en mucho tiempo. Upvoting solo por eso :)
keaukraine
62
Un buen compilador moderno lo verá x / 2y optimizará parax >> 1
intrepidis
37
@keaukraine x / 2es mucho más amigable cuando lee el código, considerando el comentario de Chris.
d3dave
¿Qué hay bufferen este ejemplo? ¿Es el canvaspasado al draw(Canvas)método?
AutonomousApps
@AutonomousApps sí, es el lienzo
Chisko
16

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

Otra forma de averiguar la altura es

mPaint.getTextSize();
kimnod
fuente
3

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

intrepidis
fuente
2

Simplemente puede obtener el tamaño de texto para un objeto Paint usando 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();
moondroid
fuente
¿De dónde 24.0fviene el?
Erigami
24.0f es solo un ejemplo para un tamaño de texto
moondroid
1

Debe usar Rect.width()y Rect.Height()que regresó de en su getTextBounds()lugar. Funciona para mi.

Putti
fuente
2
No si se trata de múltiples segmentos de textos. La razón está en mi respuesta anterior.
Nar Gar
0

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 personaje, 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();
    }
}
Hesam
fuente
3
-1 para asignaciones en onDraw (): produciría una tonelada de beneficios de rendimiento si declarara los campos en la clase (iniciar en constructor) y los reutilizara en onDraw ().
zoltish