Messen der Texthöhe, die auf Leinwand (Android) gezeichnet werden soll

132

Gibt es eine einfache Möglichkeit, die Höhe von Text zu messen? Die Art und Weise, wie ich es jetzt mache, besteht darin, mithilfe von Farben measureText()die Breite zu ermitteln und dann durch Ausprobieren einen Wert zu finden, um eine ungefähre Höhe zu erhalten. Ich habe auch herumgespielt FontMetrics, aber all dies scheinen ungefähre Methoden zu sein, die scheiße sind.

Ich versuche Dinge für verschiedene Auflösungen zu skalieren. Ich kann es schaffen, aber am Ende habe ich unglaublich ausführlichen Code mit vielen Berechnungen, um die relativen Größen zu bestimmen. Ich hasse es! Es muss einen besseren Weg geben.

Danedo
quelle

Antworten:

136

Was ist mit paint.getTextBounds () (Objektmethode)

Bramp
quelle
1
Dies führt zu sehr merkwürdigen Ergebnissen, wenn ich die Höhe eines Textes bewerte. Ein kurzer Text ergibt eine Höhe von 12, während ein WIRKLICH langer Text eine Höhe von 16 ergibt (bei einer Schriftgröße von 16). Macht für mich keinen Sinn (android 2.3.3)
AgentKnopf
35
Die Varianz in der Höhe ist, wo Sie Absteiger im Text haben, dh 'Hoch' ist größer als 'Niedrig' wegen des Teils des g unter der Linie
FrinkTheBrave
208

Es gibt verschiedene Möglichkeiten, die Höhe zu messen, je nachdem, was Sie benötigen.

getTextBounds

Wenn Sie so etwas wie eine kleine Menge festen Textes genau zentrieren, möchten Sie wahrscheinlich getTextBounds. Sie können das Begrenzungsrechteck wie folgt erhalten

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

Wie Sie in den folgenden Bildern sehen können, ergeben unterschiedliche Zeichenfolgen unterschiedliche Höhen (rot dargestellt).

Geben Sie hier die Bildbeschreibung ein

Diese unterschiedlichen Höhen können in einigen Situationen von Nachteil sein, wenn Sie nur eine konstante Höhe benötigen, unabhängig davon, um welchen Text es sich handelt. Siehe den nächsten Abschnitt.

Paint.FontMetrics

Sie können die Höhe der Schrift aus den Schriftmetriken berechnen. Die Höhe ist immer gleich, da sie aus der Schriftart und nicht aus einer bestimmten Textzeichenfolge stammt.

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

Die Grundlinie ist die Zeile, auf der sich der Text befindet. Der Abstieg ist im Allgemeinen am weitesten, wenn ein Charakter die Linie unterschreitet, und der Aufstieg ist im Allgemeinen am weitesten, wenn ein Charakter die Linie überschreitet. Um die Höhe zu erhalten, müssen Sie den Aufstieg subtrahieren, da dies ein negativer Wert ist. (Die Grundlinie ist y=0und yverringert den Bildschirm.)

Schauen Sie sich das folgende Bild an. Die Höhen für beide Saiten sind 234.375.

Geben Sie hier die Bildbeschreibung ein

Wenn Sie die Zeilenhöhe und nicht nur die Texthöhe möchten, können Sie Folgendes tun:

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

Dies sind die bottom und topder Linie. Der führende (Zwischenzeilenabstand) ist normalerweise Null, aber Sie sollten ihn trotzdem hinzufügen.

Die obigen Bilder stammen von diesem Projekt . Sie können damit herumspielen, um zu sehen, wie Schriftmetriken funktionieren.

StaticLayout

Zum Messen der Höhe von mehrzeiligem Text sollten Sie a verwenden StaticLayout. Ich habe in dieser Antwort ausführlich darüber gesprochen , aber der grundlegende Weg, um diese Höhe zu erreichen, ist folgender:

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
quelle
Schöne Erklärung. Aus welcher App stammen die Screenshots?
Micheal Johnson
1
@MichealJohnson, ich habe die App hier als GitHub-Projekt hinzugefügt .
Suragch
1
Was gibt Ihnen "getTextSize" dann?
Android-Entwickler
1
Mit Paint's erhalten getTextSize()Sie die Schriftgröße in Pixeleinheiten (im Gegensatz zu spEinheiten). @androiddeveloper
Suragch
2
Wie ist das Verhältnis der Schriftgröße in Pixeleinheiten zur gemessenen Höhe und zu den FontMetrics-Abmessungen ? Das ist eine Frage, die ich gerne näher untersuchen möchte.
Suragch
85

Die Antwort von @ bramp ist richtig - teilweise insofern, als nicht erwähnt wird, dass die berechneten Grenzen das minimale Rechteck sind, das den Text vollständig mit impliziten Startkoordinaten von 0, 0 enthält.

Dies bedeutet, dass sich die Höhe von beispielsweise "Py" von der Höhe von "py" oder "hi" oder "oi" oder "aw" unterscheidet, da sie pixelweise unterschiedliche Höhen erfordern.

Dies entspricht keineswegs FontMetrics in klassischem Java.

Während die Breite eines Textes kein großer Schmerz ist, ist es die Höhe.

Wenn Sie den gezeichneten Text vertikal zentrieren möchten, versuchen Sie insbesondere, die Grenzen des Textes "a" (ohne Anführungszeichen) zu ermitteln, anstatt den Text zu verwenden, den Sie zeichnen möchten. Funktioniert bei mir...

Folgendes meine ich:

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

Vertikal zentrieren des Textes bedeutet, dass das Begrenzungsrechteck vertikal zentriert wird - was für verschiedene Texte (Großbuchstaben, lange Buchstaben usw.) unterschiedlich ist. Wir möchten aber auch die Basislinien der gerenderten Texte so ausrichten, dass sie nicht erhöht oder gerillt erscheinen. Solange wir also die Mitte des kleinsten Buchstabens kennen (z. B. "a"), können wir seine Ausrichtung für den Rest der Texte wiederverwenden. Dadurch werden alle Texte zentriert und an der Grundlinie ausgerichtet.

Nar Gar
quelle
25
Seit x >> 1Ewigkeiten nicht mehr gesehen . Upvoting nur dafür :)
Keaukraine
61
Ein guter moderner Compiler wird es sehen x / 2und optimierenx >> 1
intrepidis
37
@keaukraine x / 2ist viel freundlicher beim Lesen von Code, wenn man Chris 'Kommentar berücksichtigt.
d3dave
Was ist bufferin diesem Beispiel? Ist es die canvasan die draw(Canvas)Methode übergeben?
AutonomousApps
@ AutonomousApps ja, es ist die Leinwand
Chisko
16

Die Höhe ist die Textgröße, die Sie für die Paint-Variable festgelegt haben.

Eine andere Möglichkeit, die Höhe herauszufinden, ist

mPaint.getTextSize();
Kimnod
quelle
3

Sie können die android.text.StaticLayoutKlasse verwenden, um die erforderlichen Grenzen anzugeben und dann aufzurufen getHeight(). Sie können den Text (im Layout enthalten) zeichnen, indem Sie seine draw(Canvas)Methode aufrufen .

unerschrocken
quelle
2

Sie können die Textgröße für ein Paint-Objekt einfach mit der Methode getTextSize () abrufen. Beispielsweise:

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();
Monddroide
quelle
Woher kommt das 24.0f?
Erigami
24.0f es ist nur ein Beispiel für eine Textgröße
moondroid
1

Sie müssen verwenden Rect.width()und Rect.Height()welche von getTextBounds()stattdessen zurückgegeben. Das ist für mich in Ordnung.

Putti
quelle
2
Nicht, wenn Sie mit mehreren Textsegmenten arbeiten. Der Grund ist in meiner Antwort oben.
Nar Gar
0

Wenn noch jemand ein Problem hat, ist dies mein Code.

Ich habe eine benutzerdefinierte Ansicht, die quadratisch ist (Breite = Höhe), und ich möchte ihr ein Zeichen zuweisen. onDraw()zeigt, wie man die Höhe des Charakters erreicht, obwohl ich es nicht benutze. Das Zeichen wird in der Mitte der Ansicht angezeigt.

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
quelle
3
-1 für Zuweisungen in onDraw (): Es würde eine Menge Leistungsvorteile bringen, wenn Sie die Felder in der Klasse deklarieren (im Konstruktor initiieren) und sie in onDraw () wiederverwenden würden.
zoltish