Medindo a altura do texto a ser desenhado no Canvas (Android)

132

Alguma maneira direta de medir a altura do texto? A maneira como estou fazendo isso agora é usando o Paint measureText()para obter a largura e, em seguida, por tentativa e erro, encontrando um valor para obter uma altura aproximada. Eu também ando brincando FontMetrics, mas todos esses parecem métodos aproximados que são ruins.

Estou tentando escalar as coisas para diferentes resoluções. Eu posso fazer isso, mas acabo com um código incrivelmente detalhado com muitos cálculos para determinar tamanhos relativos. Eu odeio isso! Tem que haver uma maneira melhor.

Danedo
fonte

Respostas:

136

E quanto a paint.getTextBounds () (método de objeto)

borda
fonte
1
Isso produz resultados muito estranhos, quando avalio a altura de um texto. Um texto curto resulta em uma altura de 12, enquanto um texto REALMENTE longo resulta em uma altura de 16 (dado um tamanho de fonte de 16). Não faz sentido para mim (android 2.3.3)
AgentKnopf
35
A variação de altura é onde você tem descenders no texto, ou seja, 'High' é mais alto do que 'Low' por causa da parte do g abaixo da linha
FrinkTheBrave
208

Existem diferentes maneiras de medir a altura, dependendo do que você precisa.

getTextBounds

Se você está fazendo algo como centralizar com precisão uma pequena quantidade de texto fixo, provavelmente deseja getTextBounds. Você pode obter o retângulo delimitador como este

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

Como você pode ver nas imagens a seguir, cordas diferentes fornecerão alturas diferentes (mostradas em vermelho).

insira a descrição da imagem aqui

Essas alturas diferentes podem ser uma desvantagem em algumas situações em que você só precisa de uma altura constante, independentemente do texto. Veja a próxima seção.

Paint.FontMetrics

Você pode calcular a altura da fonte a partir das métricas da fonte. A altura é sempre a mesma porque é obtida a partir da fonte, não de qualquer sequência de texto específica.

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

A linha de base é a linha em que o texto se localiza. A descida é geralmente a mais longe que um personagem vai abaixo da linha e a subida é geralmente a mais longe que um personagem fica acima da linha. Para obter a altura, você deve subtrair a subida, porque é um valor negativo. (A linha de base é y=0e ydiminui a tela.)

Veja a seguinte imagem. As alturas para as duas cordas são 234.375.

insira a descrição da imagem aqui

Se você deseja a altura da linha, e não apenas a altura do texto, faça o seguinte:

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

Estes são os bottome topda linha. O início (espaçamento entre linhas) geralmente é zero, mas você deve adicioná-lo de qualquer maneira.

As imagens acima vêm deste projeto . Você pode brincar com ele para ver como as métricas de fonte funcionam.

StaticLayout

Para medir a altura do texto com várias linhas, use a StaticLayout. Eu falei sobre isso em detalhes nesta resposta , mas a maneira básica de obter essa altura é assim:

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
fonte
Boa explicação. De que aplicativo são as capturas de tela?
Micheal Johnson
1
@MichealJohnson, adicionei o aplicativo como um projeto do GitHub aqui .
Suragch 03/03
1
O que "getTextSize" oferece a você, então?
desenvolvedor android
1
O Paint's getTextSize()fornece o tamanho da fonte em unidades de pixel (ao contrário de spunidades). @androiddeveloper
Suragch
2
Qual é a relação do tamanho da fonte em unidades de pixel com a altura medida e as dimensões do FontMetrics ? Essa é uma pergunta que eu gostaria de explorar mais.
Suragch
85

A resposta do @ bramp está correta - parcialmente, na medida em que não menciona que os limites calculados serão o retângulo mínimo que contém o texto totalmente com coordenadas de início implícitas de 0, 0.

Isso significa que a altura de, por exemplo, "Py" será diferente da altura de "py" ou "oi" ou "oi" ou "aw" porque em termos de pixel eles exigem alturas diferentes.

Isso nunca é equivalente ao FontMetrics no java clássico.

Embora a largura de um texto não seja muito dolorosa, a altura é.

Em particular, se você precisar alinhar verticalmente o texto desenhado, tente obter os limites do texto "a" (sem aspas), em vez de usar o texto que você deseja desenhar. Funciona para mim...

Aqui está o que eu quero dizer:

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

Alinhar verticalmente o texto ao centro significa alinhar verticalmente o retângulo delimitador - o que é diferente para textos diferentes (maiúsculas, letras longas etc.). Mas o que realmente queremos fazer é também alinhar as linhas de base dos textos renderizados, para que eles não pareçam elevados ou ranhurados. Assim, desde que conheçamos o centro da menor letra ("a" por exemplo), poderemos reutilizar seu alinhamento pelo restante dos textos. Isso centralizará o alinhamento de todos os textos, bem como o alinhamento da linha de base.

Nar Gar
fonte
25
Não vejo x >> 1há séculos. Upvoting apenas para que :)
keaukraine
61
Um bom compilador moderno vai ver x / 2e otimizá-lo parax >> 1
intrepidis
37
@keaukraine x / 2é muito mais amigável ao ler código, considerando o comentário de Chris.
D3dave
o que há bufferneste exemplo? É o canvaspassado para o draw(Canvas)método?
AutonomousApps
@AutonomousApps sim, é a tela
Chisko
16

A altura é o tamanho do texto que você definiu na variável Paint.

Outra maneira de descobrir a altura é

mPaint.getTextSize();
kimnod
fonte
3

Você pode usar a android.text.StaticLayoutclasse para especificar os limites necessários e depois chamar getHeight(). Você pode desenhar o texto (contido no layout) chamando seu draw(Canvas)método.

intrepidis
fonte
2

Você pode simplesmente obter o tamanho do texto de um objeto Paint usando o método getTextSize (). Por exemplo:

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
fonte
De onde 24.0fvem isso?
Erigami 17/02
24.0f é apenas um exemplo de tamanho de texto
moondroid 17/02/2015
1

Você deve usar Rect.width()e do Rect.Height()qual retornou getTextBounds(). Isso funciona para mim.

Putti
fonte
2
Não se você estiver lidando com vários segmentos de textos. O motivo está na minha resposta acima.
Nar Gar
0

Se alguém ainda tiver problemas, este é o meu código.

Tenho uma exibição personalizada quadrada (largura = altura) e desejo atribuir um caractere a ela. onDraw()mostra como obter altura do personagem, embora não o esteja usando. O personagem será exibido no meio da 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
fonte
3
-1 para alocações no onDraw (): traria muitos benefícios de desempenho se você declarasse os campos na classe (iniciada no construtor) e os reutilizasse no onDraw ().
Zoltish