Misurare l'altezza del testo da disegnare su Canvas (Android)


132

Qualche modo semplice per misurare l'altezza del testo? Il modo in cui lo sto facendo ora è usare Paint measureText()per ottenere la larghezza, quindi per tentativi ed errori trovare un valore per ottenere un'altezza approssimativa. Ho anche scherzato FontMetrics, ma tutti questi sembrano metodi approssimativi che fanno schifo.

Sto cercando di ridimensionare le cose per risoluzioni diverse. Posso farlo, ma finisco con un codice incredibilmente dettagliato con molti calcoli per determinare le dimensioni relative. Lo odio! Deve esserci un modo migliore.

Risposte:


136

Che dire di paint.getTextBounds () (metodo oggetto)


1
Questo produce risultati molto strani, quando valuto l'altezza di un testo. Un testo breve porta a un'altezza di 12, mentre un testo DAVVERO lungo porta a un'altezza di 16 (con una dimensione del carattere di 16). Non ha senso per me (Android 2.3.3)
AgentKnopf,

36
La varianza in altezza è dove hai discendenti nel testo, cioè "Alto" è più alto di "Basso" a causa della parte della g sotto la linea
FrinkTheBrave

208

Esistono diversi modi per misurare l'altezza in base a ciò di cui hai bisogno.

getTextBounds

Se stai facendo qualcosa come centrare con precisione una piccola quantità di testo fisso, probabilmente vorrai getTextBounds. Puoi ottenere il rettangolo di delimitazione in questo modo

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

Come puoi vedere per le seguenti immagini, stringhe diverse daranno altezze diverse (mostrate in rosso).

inserisci qui la descrizione dell'immagine

Queste altezze diverse potrebbero essere uno svantaggio in alcune situazioni in cui hai solo bisogno di un'altezza costante, indipendentemente dal testo. Vedi la prossima sezione

Paint.FontMetrics

È possibile calcolare l'altezza del carattere dalla metrica del carattere. L'altezza è sempre la stessa perché è ottenuta dal carattere, non da una particolare stringa di testo.

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

La linea di base è la linea su cui si trova il testo. La discesa è generalmente la più lontana da un personaggio sotto la linea e la salita è generalmente la più lontana da un personaggio sopra la linea. Per ottenere l'altezza devi sottrarre la salita perché è un valore negativo. (La linea di base è y=0e yriduce lo schermo.)

Guarda l'immagine seguente. Le altezze per entrambe le corde sono 234.375.

inserisci qui la descrizione dell'immagine

Se si desidera l'altezza della linea anziché solo l'altezza del testo, è possibile effettuare le seguenti operazioni:

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

Questi sono i bottome topdella linea. La spaziatura iniziale (interlinea) è generalmente zero, ma è necessario aggiungerla comunque.

Le immagini sopra provengono da questo progetto . Puoi giocarci attorno per vedere come funzionano le metriche dei caratteri.

StaticLayout

Per misurare l'altezza del testo su più righe devi usare a StaticLayout. Ne ho parlato in dettaglio in questa risposta , ma il modo di base per ottenere questa altezza è il seguente:

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

Bella spiegazione. Da quale app provengono gli screenshot?
Micheal Johnson,

1
@MichealJohnson, ho aggiunto l'app come progetto GitHub qui .
Suragch

1
Cosa ti offre "getTextSize"?
sviluppatore Android

1
Paint ti getTextSize()dà la dimensione del carattere in pixel (anziché in spunità). @androiddeveloper
Suragch,

2
Qual è la relazione tra la dimensione del carattere in pixel e l'altezza misurata e le dimensioni di FontMetrics ? Questa è una domanda che vorrei esplorare di più.
Suragch,

85

La risposta di @ bramp è corretta - parzialmente, in quanto non menziona il fatto che i limiti calcolati saranno il rettangolo minimo che contiene il testo completamente con coordinate di inizio implicite di 0, 0.

Ciò significa che l'altezza di, ad esempio, "Py" sarà diversa dall'altezza di "py" o "hi" o "oi" o "aw" perché in termini di pixel richiedono altezze diverse.

Questo non è affatto equivalente a FontMetrics nel java classico.

Mentre la larghezza di un testo non è un gran problema, l'altezza lo è.

In particolare, se è necessario allineare verticalmente al centro il testo disegnato, provare a ottenere i contorni del testo "a" (senza virgolette), anziché utilizzare il testo che si desidera disegnare. Per me va bene...

Ecco cosa intendo:

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

Centrare verticalmente l'allineamento del testo significa allineare verticalmente il centro del rettangolo di delimitazione, che è diverso per testi diversi (maiuscole, lettere lunghe ecc.). Ma ciò che vogliamo effettivamente fare è allineare anche le linee di base dei testi renderizzati, in modo che non appaiano elevati o scanalati. Quindi, fintanto che conosciamo il centro della lettera più piccola (ad esempio "a"), possiamo riutilizzare il suo allineamento per il resto dei testi. Ciò centrerà l'allineamento di tutti i testi e l'allineamento di base.


25
Non vedo x >> 1da secoli. Upvoting solo per quello :)
keaukraine,

62
Un buon compilatore moderno lo vedrà x / 2e lo ottimizzeràx >> 1
intrepidis

37
@keaukraine x / 2è molto più amichevole quando legge il codice, considerando il commento di Chris.
d3dave,

cosa c'è bufferin questo esempio? È canvaspassato al draw(Canvas)metodo?
AutonomousApps

@AutonomousApps sì, è la tela
Chisko,

16

L'altezza è la dimensione del testo che hai impostato sulla variabile Paint.

Un altro modo per scoprire l'altezza è

mPaint.getTextSize();

3

È possibile utilizzare la android.text.StaticLayoutclasse per specificare i limiti richiesti e quindi chiamaregetHeight() . Puoi disegnare il testo (contenuto nel layout) chiamando il suo draw(Canvas)metodo.


2

Puoi semplicemente ottenere la dimensione del testo per un oggetto Paint usando il metodo getTextSize (). Per esempio:

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

Da dove 24.0fviene?
Erigami,

24.0f è solo un esempio per una dimensione del testo
moondroid,

1

È necessario utilizzare Rect.width()e Rect.Height()che è restituito da getTextBounds()invece. Per me va bene.


2
Non se hai a che fare con più segmenti di testo. Il motivo è nella mia risposta sopra.
Nar Gar,

0

Se qualcuno ha ancora problemi, questo è il mio codice.

Ho una vista personalizzata che è quadrata (larghezza = altezza) e voglio assegnargli un personaggio. onDraw()mostra come ottenere l'altezza del personaggio, anche se non lo sto usando. Il personaggio verrà visualizzato al centro della 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();
    }
}

3
-1 per le allocazioni in onDraw (): produrrebbe una tonnellata di vantaggi in termini di prestazioni se dovessi dichiarare i campi nella classe (avviare nel costruttore) e riutilizzarli in onDraw ().
zoltish
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.