Pomiar wysokości tekstu do narysowania na płótnie (Android)

134

Jakiś prosty sposób na zmierzenie wysokości tekstu? Sposób, w jaki teraz to robię, polega na użyciu programu Paint, measureText()aby uzyskać szerokość, a następnie metodą prób i błędów znajdując wartość, aby uzyskać przybliżoną wysokość. Ja też się bawiłem FontMetrics, ale wszystkie te wydają się być przybliżonymi metodami, które są do niczego.

Próbuję skalować rzeczy dla różnych rozdzielczości. Mogę to zrobić, ale otrzymuję niesamowicie rozwlekły kod z wieloma obliczeniami w celu określenia względnych rozmiarów. Nienawidzę tego! Musi być lepszy sposób.

Danedo
źródło

Odpowiedzi:

136

A co z paint.getTextBounds () (metoda obiektu)

bramp
źródło
1
Daje to bardzo dziwne wyniki, gdy oceniam wysokość tekstu. Krótki tekst ma wysokość 12, a NAPRAWDĘ długi tekst ma wysokość 16 (przy rozmiarze czcionki 16). To nie ma dla mnie sensu (Android 2.3.3)
AgentKnopf,
37
Różnica w wysokości występuje tam, gdzie w tekście znajdują się
stopnie
223

Istnieją różne sposoby pomiaru wysokości w zależności od potrzeb.

getTextBounds

Jeśli robisz coś takiego jak precyzyjne wyśrodkowanie niewielkiej ilości stałego tekstu, prawdopodobnie chcesz getTextBounds. Możesz uzyskać prostokąt ograniczający w ten sposób

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

Jak widać na poniższych obrazkach, różne struny będą miały różne wysokości (pokazane na czerwono).

wprowadź opis obrazu tutaj

Te różne wysokości mogą być wadą w niektórych sytuacjach, gdy potrzebna jest po prostu stała wysokość bez względu na tekst. Zobacz następną sekcję.

Paint.FontMetrics

Możesz obliczyć wysokość czcionki na podstawie metryk czcionki. Wysokość jest zawsze taka sama, ponieważ jest pobierana z czcionki, a nie z konkretnego ciągu tekstowego.

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

Linia bazowa to linia, na której znajduje się tekst. Zejście jest na ogół najdalej, na jaką postać znajdzie się poniżej linii, a wzniesienie jest na ogół najdalej, na jaką postać znajdzie się nad linią. Aby uzyskać wysokość, musisz odjąć wzrost, ponieważ jest to wartość ujemna. (Linia bazowa jest y=0i yzmniejsza się w górę ekranu).

Spójrz na poniższy obraz. Wysokości obu strun to 234.375.

wprowadź opis obrazu tutaj

Jeśli chcesz mieć wysokość wiersza, a nie tylko wysokość tekstu, możesz wykonać następujące czynności:

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

To są bottomi toplinii. Początek (odstępy między wierszami) są zwykle równe zero, ale mimo to należy je dodać.

Powyższe obrazy pochodzą z tego projektu . Możesz się nim bawić, aby zobaczyć, jak działają wskaźniki czcionki.

StaticLayout

Aby zmierzyć wysokość tekstu wielowierszowego, należy użyć rozszerzenia StaticLayout. Mówiłem o tym szczegółowo w tej odpowiedzi , ale podstawowy sposób uzyskania tej wysokości jest taki:

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
źródło
Niezłe wyjaśnienie. Z jakiej aplikacji są zrzuty ekranu?
Micheal Johnson
1
@MichealJohnson, dodałem aplikację jako projekt GitHub tutaj .
Suragch
1
Co w takim razie daje „getTextSize”?
programista Androida
1
Paint's getTextSize()podaje rozmiar czcionki w pikselach (w przeciwieństwie do spjednostek). @androiddeveloper
Suragch
2
Jaka jest zależność między wielkością czcionki w pikselach a zmierzoną wysokością i wymiarami FontMetrics ? To jest kwestia, którą chciałbym dokładniej zbadać.
Suragch
86

Odpowiedź @brampa jest poprawna - częściowo, ponieważ nie wspomina, że ​​obliczone granice będą minimalnym prostokątem zawierającym cały tekst z niejawnymi współrzędnymi początkowymi równymi 0, 0.

Oznacza to, że wysokość na przykład „Py” będzie inna niż wysokość „py” lub „hi”, „oi” lub „aw”, ponieważ pod względem pikseli wymagają one różnych wysokości.

W żadnym wypadku nie jest to odpowiednik FontMetrics w klasycznej Javie.

Chociaż szerokość tekstu nie jest wielkim problemem, wysokość tak.

W szczególności, jeśli chcesz wyśrodkować narysowany tekst w pionie, spróbuj uzyskać granice tekstu „a” (bez cudzysłowów), zamiast używać tekstu, który zamierzasz narysować. Pracuje dla mnie...

Oto o co mi chodzi:

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

Wyśrodkowanie tekstu w pionie oznacza wyśrodkowanie tekstu w pionie - wyrównanie prostokąta ograniczającego - co jest różne dla różnych tekstów (wielkie litery, długie litery itp.). Ale w rzeczywistości chcemy również wyrównać linie bazowe renderowanych tekstów, tak aby nie wyglądały na podniesione lub wyżłobione. Tak więc, jeśli znamy środek najmniejszej litery (na przykład „a”), możemy ponownie wykorzystać jego wyrównanie w pozostałych tekstach. Spowoduje to wyśrodkowanie wszystkich tekstów, a także wyrównanie ich do linii bazowej.

Nar Gar
źródło
25
Nie widziałem x >> 1od wieków. Głosowanie tylko za tym :)
keaukraine
62
Dobry, nowoczesny kompilator zobaczy x / 2i zoptymalizuje go dox >> 1
intrepidis
37
@keaukraine x / 2jest dużo bardziej przyjazny podczas czytania kodu, biorąc pod uwagę komentarz Chrisa.
d3dave
co jest bufferw tym przykładzie? Czy jest to canvasprzekazane do draw(Canvas)metody?
AutonomousApps
@AutonomousApps tak, to płótno
Chisko
16

Wysokość to rozmiar tekstu ustawiony w zmiennej Paint.

Innym sposobem sprawdzenia wysokości jest

mPaint.getTextSize();
kimnod
źródło
3

Możesz użyć android.text.StaticLayoutklasy, aby określić wymagane granice, a następnie wywołać getHeight(). Możesz narysować tekst (zawarty w układzie), wywołując jego draw(Canvas)metodę.

intrepidis
źródło
2

Możesz po prostu pobrać rozmiar tekstu dla obiektu Paint za pomocą metody getTextSize (). Na przykład:

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
źródło
Skąd się 24.0fbierze?
Erigami,
24.0f to tylko przykład rozmiaru tekstu
moondroid
1

Musisz użyć Rect.width()i Rect.Height()który wrócił z getTextBounds()zamiast. To działa dla mnie.

Putti
źródło
2
Nie, jeśli masz do czynienia z wieloma segmentami tekstów. Powód jest w mojej odpowiedzi powyżej.
Nar Gar
0

Jeśli ktoś nadal ma problem, to jest mój kod.

Mam niestandardowy widok, który jest kwadratowy (szerokość = wysokość) i chcę przypisać do niego znak. onDraw()pokazuje, jak uzyskać wysokość znaku, chociaż go nie używam. Postać zostanie wyświetlona na środku widoku.

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
źródło
3
-1 dla alokacji w onDraw (): Zadeklarowanie pól w klasie (zainicjowanie w konstruktorze) i ponowne ich użycie w onDraw () przyniosłoby mnóstwo korzyści w zakresie wydajności.
zoltish