Android Paint: .measureText () ve .getTextBounds () karşılaştırması


191

Metni kullanarak ölçüyorum Paint.getTextBounds(), çünkü görüntülenecek metnin hem yüksekliğini hem de genişliğini elde etmek istiyorum. Ancak, işlenen fiili metin her zaman biraz daha geniştir .width()ait Recttarafından doldurulan bilgi getTextBounds().

Şaşırtıcı bir şekilde, test ettim .measureText()ve farklı (daha yüksek) bir değer döndürdüğünü buldum. Denedim ve doğru buldum.

Neden farklı genişlikler rapor ediyorlar? Yüksekliği ve genişliği nasıl doğru bir şekilde alabilirim? Yani, Ben yapabilirsiniz kullanın .measureText()ama güveneyim o zaman Bilemem .height()tarafından döndürülen getTextBounds().

İstendiği gibi, sorunu yeniden oluşturmak için minimum kod İşte:

final String someText = "Hello. I believe I'm some text!";

Paint p = new Paint();
Rect bounds = new Rect();

for (float f = 10; f < 40; f += 1f) {
    p.setTextSize(f);

    p.getTextBounds(someText, 0, someText.length(), bounds);

    Log.d("Test", String.format(
        "Size %f, measureText %f, getTextBounds %d",
        f,
        p.measureText(someText),
        bounds.width())
    );
}

Çıktı, farkın sadece 1'den fazla olmadığını (ve son dakika yuvarlama hatası olmadığını) değil, aynı zamanda boyutla birlikte arttığını gösteriyor (daha fazla sonuç çıkarmak üzereydim, ancak tamamen yazı tipine bağlı olabilir):

D/Test    (  607): Size 10.000000, measureText 135.000000, getTextBounds 134
D/Test    (  607): Size 11.000000, measureText 149.000000, getTextBounds 148
D/Test    (  607): Size 12.000000, measureText 156.000000, getTextBounds 155
D/Test    (  607): Size 13.000000, measureText 171.000000, getTextBounds 169
D/Test    (  607): Size 14.000000, measureText 195.000000, getTextBounds 193
D/Test    (  607): Size 15.000000, measureText 201.000000, getTextBounds 199
D/Test    (  607): Size 16.000000, measureText 211.000000, getTextBounds 210
D/Test    (  607): Size 17.000000, measureText 225.000000, getTextBounds 223
D/Test    (  607): Size 18.000000, measureText 245.000000, getTextBounds 243
D/Test    (  607): Size 19.000000, measureText 251.000000, getTextBounds 249
D/Test    (  607): Size 20.000000, measureText 269.000000, getTextBounds 267
D/Test    (  607): Size 21.000000, measureText 275.000000, getTextBounds 272
D/Test    (  607): Size 22.000000, measureText 297.000000, getTextBounds 294
D/Test    (  607): Size 23.000000, measureText 305.000000, getTextBounds 302
D/Test    (  607): Size 24.000000, measureText 319.000000, getTextBounds 316
D/Test    (  607): Size 25.000000, measureText 330.000000, getTextBounds 326
D/Test    (  607): Size 26.000000, measureText 349.000000, getTextBounds 346
D/Test    (  607): Size 27.000000, measureText 357.000000, getTextBounds 354
D/Test    (  607): Size 28.000000, measureText 369.000000, getTextBounds 365
D/Test    (  607): Size 29.000000, measureText 396.000000, getTextBounds 392
D/Test    (  607): Size 30.000000, measureText 401.000000, getTextBounds 397
D/Test    (  607): Size 31.000000, measureText 418.000000, getTextBounds 414
D/Test    (  607): Size 32.000000, measureText 423.000000, getTextBounds 418
D/Test    (  607): Size 33.000000, measureText 446.000000, getTextBounds 441
D/Test    (  607): Size 34.000000, measureText 455.000000, getTextBounds 450
D/Test    (  607): Size 35.000000, measureText 468.000000, getTextBounds 463
D/Test    (  607): Size 36.000000, measureText 474.000000, getTextBounds 469
D/Test    (  607): Size 37.000000, measureText 500.000000, getTextBounds 495
D/Test    (  607): Size 38.000000, measureText 506.000000, getTextBounds 501
D/Test    (  607): Size 39.000000, measureText 521.000000, getTextBounds 515

[Benim yolumu] [1] görebilirsiniz. Bu pozisyon ve doğru sınır elde edebilirsiniz. [1]: stackoverflow.com/a/19979937/1621354
Alex Chi

Yanıtlar:


370

Böyle bir sorunu incelemek için yaptığım şeyi yapabilirsiniz:

Android kaynak kodunu, Paint.java kaynağını inceleyin, hem tedbirText hem de getTextBounds yöntemlerine bakın. MeasText'in native_measureText'i çağırdığını ve getTextBounds'un C ++ 'da uygulanan yerel yöntemler olan nativeGetStringBounds'u çağırdığını öğreneceksiniz.

Böylece, her ikisini de uygulayan Paint.cpp okumaya devam edersiniz.

native_measureText -> SkPaintGlue :: ölçü birimi Metin_CII

nativeGetStringBounds -> SkPaintGlue :: getStringBounds

Şimdi çalışmanız bu yöntemlerin nerede farklı olduğunu kontrol ediyor. Bazı param kontrollerinden sonra, her ikisi de Skia Lib'de (Android'in bir parçası) SkPaint :: measText işlevini çağırır, ancak her ikisi de farklı aşırı yüklenmiş formu çağırır.

Skia'ya daha fazla baktığımda, her iki çağrının da aynı fonksiyonda aynı hesaplama ile sonuçlandığını görüyorum, sadece sonucu farklı döndürüyoruz.

Sorunuzu cevaplamak için: Her iki görüşmeniz de aynı hesaplamayı yapar. Olası sonuç farkı, getTextBounds öğesinin sınırları tamsayı olarak döndürmesi, tedbirText ise float değerini döndürmesidir.

Yani ne olsun şamandıra int dönüşümü sırasında yuvarlama hatası, ve bu SkPect :: roundOut işlev çağrısı SkPaintGlue :: doTextBounds Paint.cpp olur.

Bu iki aramanın hesaplanan genişliği arasındaki fark en fazla 1 olabilir.

EDIT 4 Eki 2011

Görselleştirmeden daha iyi ne olabilir? Kendi keşif ve lütuf hak için çaba harcadım :)

resim açıklamasını buraya girin

Bu yazı tipi boyutu 60, kırmızı sınır dikdörtgen, mor ölçü_ sonucudur.

Sol bölümün sınırlarının soldan bazı pikseller başlattığı ve ölçü metninin değerinin hem sol hem de sağdaki bu değerle arttığı görülür. Bu, Glif'in AdvanceX değeri olarak adlandırılan bir şeydir. (Bunu SkPaint.cpp'deki Skia kaynaklarında keşfettim)

Bu nedenle testin sonucu, tedbirText'in her iki taraftaki metne bir miktar ilerleme değeri katması, getTextBounds ise verilen metnin sığacağı yerlerde minimum sınırları hesaplamasıdır.

Umarım bu sonuç sizin için yararlıdır.

Test kodu:

  protected void onDraw(Canvas canvas){
     final String s = "Hello. I'm some text!";

     Paint p = new Paint();
     Rect bounds = new Rect();
     p.setTextSize(60);

     p.getTextBounds(s, 0, s.length(), bounds);
     float mt = p.measureText(s);
     int bw = bounds.width();

     Log.i("LCG", String.format(
          "measureText %f, getTextBounds %d (%s)",
          mt,
          bw, bounds.toShortString())
      );
     bounds.offset(0, -bounds.top);
     p.setStyle(Style.STROKE);
     canvas.drawColor(0xff000080);
     p.setColor(0xffff0000);
     canvas.drawRect(bounds, p);
     p.setColor(0xff00ff00);
     canvas.drawText(s, 0, bounds.bottom, p);
  }

3
Yorum yapmak için çok uzun sürdüğüm için üzgünüm, yoğun bir hafta geçirdim. C ++ kodunu incelemeye zaman ayırdığınız için teşekkür ederiz! Ne yazık ki, fark 1'den büyük ve yuvarlanmış değerler kullanıldığında bile ortaya çıkıyor - örneğin, metin boyutunu 29,0 olarak ayarlamak, tedbirText () = 263 ve getTextBounds () ile sonuçlanır.
Width

Lütfen sorunu yeniden oluşturabilecek Paint kurulumu ve ölçümü ile minimum kod gönderin.
Pointer Null

Güncellenmiş. Gecikme için tekrar özür dilerim.
slezica

Burada Rich ile birlikteyim. Harika araştırma! Ödül tamamen hak edildi ve ödüllendirildi. Zaman ve çaba için teşekkür ederiz!
slezica

16
mice Bu soruyu tekrar buldum (OP). Cevabınızın ne kadar inanılmaz olduğunu unutmuştum. Gelecekten teşekkürler! En iyi 100rp yatırım yaptım!
slezica

21

Benim bu konuda deneyimim, getTextBoundsrender yaparken kullanılan ölçülen genişlik değil, metni kapsülleyen mutlak minimal sınırlayıcı rekt dönecektir. Bunun measureTextbir satır olduğunu varsaydığını da söylemek istiyorum .

Doğru ölçüm sonuçları elde etmek için, StaticLayout etmek için, metni oluşturmak ve ölçümleri çıkarmak için.

Örneğin:

String text = "text";
TextPaint textPaint = textView.getPaint();
int boundedWidth = 1000;

StaticLayout layout = new StaticLayout(text, textPaint, boundedWidth , Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
int height = layout.getHeight();

StaticLayout hakkında bilmiyordum! getWidth () işe yaramaz, sadece costructor'da (ki widthdeğil maxWidth) geçirdiğimi döndürür, ancak getLineWith () çalışacaktır. Yükseklik eşdeğeri olmamasına rağmen getDesiredWidth () statik yöntemini de buldum. Lütfen cevabı düzeltin! Ayrıca, bu kadar çok farklı yöntemin neden farklı sonuçlar verdiğini bilmek istiyorum.
slezica

Genişlikle ilgili kötülerim. Tek bir satırda oluşturulacak metnin genişliğini istiyorsanız, düzgün measureTextçalışmalıdır. Aksi takdirde, genişlik sınırlarını biliyorsanız ( onMeasureveya içinde olduğu gibi onLayout, yukarıda gönderdiğim çözümü kullanabilirsiniz. Metin biçimini otomatik olarak yeniden boyutlandırmaya mı çalışıyorsunuz?) , çizim için gereken en küçük sınırlı rektist
Chase

Gerçekten bir TextView otomatik olarak yeniden boyutlandırmaya çalışıyorum. Yüksekliğe de ihtiyacım var, problemlerim burada başladı! MeasureWidth () aksi takdirde iyi çalışır. Bundan sonra, farklı yaklaşımların neden farklı değerler verdiğini merak ettim
Slezica

1
Ayrıca dikkat edilmesi gereken bir şey, çok satırlı bir metin görünümünün, yalnızca gerekli genişlik için değil, genişlik için match_parent'ta ölçüleceğidir ve bu da StaticLayout için yukarıdaki genişlik parametresini geçerli kılar. Bir metin yeniden boyutlandırıcıya ihtiyacınız varsa, yazımı stackoverflow.com/questions/5033012/…
Chase

Bir metin genişletme animasyonuna ihtiyacım vardı ve bu bir demet yardımcı oldu! Teşekkür ederim!
kutu

18

Farelerin cevabı harika ... İşte asıl sorunun açıklaması:

Kısa basit cevap, başlamayan Paint.getTextBounds(String text, int start, int end, Rect bounds)geri dönüşlerdir . Olduğunu, çağırarak ayarlanacaktır metnin gerçek genişliğini almak için birlikte aynı sen Rect sol konumunu eklemek gerekir () getTextBounds Boya nesne. Bunun gibi bir şey:Rect(0,0)Canvas.drawText(String text, float x, float y, Paint paint)

public int getTextWidth(String text, Paint paint) {
    Rect bounds = new Rect();
    paint.getTextBounds(text, 0, end, bounds);
    int width = bounds.left + bounds.width();
    return width;
}

Buna dikkat et bounds.left - bu sorunun anahtarıdır.

Bu şekilde kullanarak aynı genişlikte metin alırsınız. Canvas.drawText() .

Ve aynı işlev heightmetnin alınması için olmalıdır :

public int getTextHeight(String text, Paint paint) {
    Rect bounds = new Rect();
    paint.getTextBounds(text, 0, end, bounds);
    int height = bounds.bottom + bounds.height();
    return height;
}

Ps: Bu tam kodu test etmedim, ancak anlayışı test ettim.


Bu cevapta çok daha ayrıntılı bir açıklama verilmiştir .


2
Rect (b.genişliği) (b.right-b.left) ve (b.haight) olmasını (b.bottom-b.top) olarak tanımlar. Bu nedenle (b.left + b.width) öğesini (b.right) ve (b.bottom + b.height) ile (2 * b.bottom-b.top) ile değiştirebilirsiniz, ancak ( b.bottom-b.top) yerine (b.height) ile aynıdır. (C ++ kaynağını kontrol dayalı) genişliği hakkında doğru olduğunu düşünüyorum, getTextWidth () (b.right) ile aynı değeri döndürür, bazı yuvarlama hataları olabilir. (b sınırlara referanstır)
Nulano

11

Bu soruya tekrar cevap verdiğim için üzgünüm ... Görüntüyü gömmem gerekiyordu.

Bence farelerin buldukları sonuçlar yanlış. Gözlemler yazı tipi boyutu 60 için doğru olabilir, ancak metin daha küçük olduğunda daha farklı hale gelirler. Örneğin. 10px. Bu durumda metin aslında sınırların ÖTESİNDE çizilir.

resim açıklamasını buraya girin

Ekran görüntüsünün kaynak kodu:

  @Override
  protected void onDraw( Canvas canvas ) {
    for( int i = 0; i < 20; i++ ) {
      int startSize = 10;
      int curSize = i + startSize;
      paint.setTextSize( curSize );
      String text = i + startSize + " - " + TEXT_SNIPPET;
      Rect bounds = new Rect();
      paint.getTextBounds( text, 0, text.length(), bounds );
      float top = STEP_DISTANCE * i + curSize;
      bounds.top += top;
      bounds.bottom += top;
      canvas.drawRect( bounds, bgPaint );
      canvas.drawText( text, 0, STEP_DISTANCE * i + curSize, paint );
    }
  }

Ah, bu sisleme devam ediyor. Merak etmeme rağmen hala bununla ilgileniyorum. Başka bir şey bulursanız lütfen bize bildirin.
slezica

Ölçmek istediğiniz yazı tipini 20 diyelim çarpanıyla çarpıp sonucu buna böldüğünüzde sorun biraz çözülebilir. THAN sadece güvenli tarafta olmak için ölçülen boyutun% 1-2 daha genişliğini eklemek isteyebilirsiniz. Sonunda uzun olarak ölçülen bir metniniz olacak, ancak en azından üst üste binen metin yok.
Moritz

6
Metni sınırlayıcı rektetin tam olarak çizmek için X = 0.0f ile Metin çizmemelisiniz, çünkü sınırlayıcı rect genişlik ve yükseklik gibi değil rekt gibi geri döner. Doğru çizmek için "-bounds.left" değerindeki X koordinatını dengelemeniz gerekir, ardından doğru şekilde gösterilecektir.
Roma

10

YASAL UYARI: Bu çözüm, minimum genişliği belirleme açısından% 100 doğru değildir.

Bir tuval üzerindeki metnin nasıl ölçüleceğini de anlıyordum. Farelerden gelen büyük gönderiyi okuduktan sonra çok satırlı metnin nasıl ölçüleceğine dair bazı problemler yaşadım. Bu katkılardan hiçbir belirgin yolu yoktur ama bazı araştırmalardan sonra ben StaticLayout sınıf genelinde cam. Çok satırlı metni ("\ n" içeren metin) ölçmenize ve ilişkili Paint ile metninizin çok daha fazla özelliğini yapılandırmanıza olanak tanır.

Çok satırlı metnin nasıl ölçüleceğini gösteren bir snippet:

private StaticLayout measure( TextPaint textPaint, String text, Integer wrapWidth ) {
    int boundedWidth = Integer.MAX_VALUE;
    if (wrapWidth != null && wrapWidth > 0 ) {
       boundedWidth = wrapWidth;
    }
    StaticLayout layout = new StaticLayout( text, textPaint, boundedWidth, Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false );
    return layout;
}

Wrapwitdh, çok satırlı metninizi belirli bir genişlikle sınırlamak isteyip istemediğinizi belirleyebilir.

StaticLayout.getWidth () yalnızca bu boundedWidth değerini döndürdüğünden, çok satırlı metninizin gerektirdiği maksimum genişliği elde etmek için başka bir adım atmanız gerekir. Her bir çizgi genişliğini belirleyebilirsiniz ve maksimum genişlik elbette en yüksek çizgi genişliğidir:

private float getMaxLineWidth( StaticLayout layout ) {
    float maxLine = 0.0f;
    int lineCount = layout.getLineCount();
    for( int i = 0; i < lineCount; i++ ) {
        if( layout.getLineWidth( i ) > maxLine ) {
            maxLine = layout.getLineWidth( i );
        }
    }
    return maxLine;
}

Eğer geçen olmamalıdır i(Kısa süre önce her seferinde 0 geçiyoruz) getLineWidth içine
Zengin Sı

2

Metin sınırlarını tam olarak ölçmenin başka bir yolu var, önce geçerli Paint ve metin için yol almalısınız. Sizin durumunuzda şöyle olmalıdır:

p.getTextPath(someText, 0, someText.length(), 0.0f, 0.0f, mPath);

Bundan sonra arayabilirsiniz:

mPath.computeBounds(mBoundsPath, true);

Kodumda her zaman doğru ve beklenen değerleri döndürür. Ancak, yaklaşımınızdan daha hızlı çalışıp çalışmadığından emin değilim.


Ben metnin etrafında bir kontur anahat çizmek için, zaten getTextPath yaptığımdan beri bu yararlı buldum.
19:39

2

Arasında farklı getTextBoundsve measureTextaşağıdaki görüntü ile tarif edilmektedir.

Kısacası,

  1. getTextBoundstam metnin RECT değerini elde etmektir. measureTextSol ve sağ ekstra boşluk dahil metnin uzunluğu vardır.

  2. Metin arasında boşluk varsa measureText, koordinat kaydırılsa da, TextBound'un uzunluğunu içermez ancak ölçmez.

  3. Metin sola yatırılabilir (Eğriltme). Bu durumda, sınırlayıcı kutu sol tarafı ölçü metninin ölçümünün dışını aşacak ve bağlı metnin toplam uzunluğumeasureText

  4. Metin sağa eğilebilir (Eğriltilebilir). Bu durumda, sınırlayıcı kutu sağ tarafı ölçü metninin ölçümünün dışını aşacak ve bağlı metnin toplam uzunluğumeasureText

resim açıklamasını buraya girin


0

İlk harfin gerçek boyutlarını bu şekilde hesapladım (yöntem başlığını ihtiyaçlarınıza göre, yani char[]kullanım yerine değiştirebilirsiniz String):

private void calculateTextSize(char[] text, PointF outSize) {
    // use measureText to calculate width
    float width = mPaint.measureText(text, 0, 1);

    // use height from getTextBounds()
    Rect textBounds = new Rect();
    mPaint.getTextBounds(text, 0, 1, textBounds);
    float height = textBounds.height();
    outSize.x = width;
    outSize.y = height;
}

Orijinal Paint sınıfı yerine TextPaint kullanıyorum.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.