Sơn Android: .measureText () so với .getTextBound ()


191

Tôi đang đo văn bản bằng cách sử dụng Paint.getTextBounds(), vì tôi quan tâm đến việc hiển thị cả chiều cao và chiều rộng của văn bản. Tuy nhiên, các văn bản thực tế trả lại luôn luôn là một chút rộng hơn so với .width()các Rectthông tin lấp đầy bởi getTextBounds().

Thật ngạc nhiên, tôi đã thử nghiệm .measureText()và thấy rằng nó trả về một giá trị khác (cao hơn). Tôi đã thử, và thấy nó đúng.

Tại sao họ báo cáo độ rộng khác nhau? Làm thế nào tôi có thể có được chính xác chiều cao và chiều rộng? Ý tôi là, tôi có thể sử dụng .measureText(), nhưng sau đó tôi sẽ không biết liệu tôi có nên tin tưởng người .height()trở về hay không getTextBounds().

Theo yêu cầu, đây là mã tối thiểu để tái tạo vấn đề:

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

Kết quả đầu ra cho thấy sự khác biệt không chỉ lớn hơn 1 (và không có lỗi làm tròn phút cuối), mà còn có vẻ tăng theo kích thước (tôi sắp rút ra nhiều kết luận hơn, nhưng nó có thể hoàn toàn phụ thuộc vào phông chữ):

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

Bạn có thể thấy [cách của tôi] [1]. Điều đó có thể có được vị trí và ràng buộc chính xác. [1]: stackoverflow.com/a/19979937/1621354
Alex Chi

Giải thích liên quan về các phương pháp đo văn bản cho khách truy cập khác cho câu hỏi này.
Suragch

Câu trả lời:


370

Bạn có thể làm những gì tôi đã làm để kiểm tra vấn đề như vậy:

Nghiên cứu mã nguồn Android, nguồn Paint.java, xem cả hai phương thức đo lường và getTextBound. Bạn sẽ học được rằng đo lường gọi các cuộc gọi local_measureText và getTextBound gọi localGetStringBound, là các phương thức riêng được triển khai trong C ++.

Vì vậy, bạn sẽ tiếp tục nghiên cứu Paint.cpp, thực hiện cả hai.

local_measureText -> SkPaintGlue :: đoText_CII

localGetStringBound -> SkPaintGlue :: getStringBound

Bây giờ nghiên cứu của bạn kiểm tra nơi các phương pháp khác nhau. Sau một số kiểm tra param, cả hai chức năng gọi SkPaint :: đoText trong Skia Lib (một phần của Android), nhưng cả hai đều gọi dạng quá tải khác nhau.

Đi sâu hơn vào Skia, tôi thấy rằng cả hai cuộc gọi đều dẫn đến cùng một tính toán trong cùng một chức năng, chỉ trả về kết quả khác nhau.

Để trả lời câu hỏi của bạn: Cả hai cuộc gọi của bạn đều thực hiện tính toán giống nhau. Sự khác biệt có thể có của kết quả nằm ở thực tế là getTextBound trả về giới hạn dưới dạng số nguyên, trong khi đó, đo lường trả về giá trị float.

Vì vậy, những gì bạn nhận được là làm tròn lỗi trong quá trình chuyển đổi float thành int và điều này xảy ra trong Paint.cpp trong SkPaintGlue :: doTextBound trong lệnh gọi hàm SkRect :: roundOut.

Sự khác biệt giữa độ rộng tính toán của hai cuộc gọi đó có thể là tối đa 1.

EDIT ngày 4 tháng 10 năm 2011

Những gì có thể tốt hơn so với trực quan. Tôi đã nỗ lực, để khám phá riêng, và vì tiền thưởng xứng đáng :)

nhập mô tả hình ảnh ở đây

Đây là cỡ chữ 60, màu đỏ là giới hạn hình chữ nhật , màu tím là kết quả của số đo.

Người ta thấy rằng giới hạn phần bên trái bắt đầu một số pixel từ bên trái và giá trị của số đo được tăng theo giá trị này ở cả bên trái và bên phải. Đây là một thứ gọi là giá trị AdvanceX của Glyph. (Tôi đã phát hiện ra điều này trong các nguồn Skia trong SkPaint.cpp)

Vì vậy, kết quả của bài kiểm tra là số đo thêm một số giá trị nâng cao cho văn bản ở cả hai bên, trong khi getTextBound tính các giới hạn tối thiểu trong đó văn bản đã cho sẽ phù hợp.

Hy vọng kết quả này hữu ích cho bạn.

Mã kiểm tra:

  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
Xin lỗi vì mất quá nhiều thời gian để bình luận, tôi đã có một tuần bận rộn. Cảm ơn bạn đã dành thời gian để đào sâu vào mã C ++! Thật không may, sự khác biệt lớn hơn 1 và nó xảy ra ngay cả khi sử dụng các giá trị được làm tròn - ví dụ: đặt kích thước văn bản thành 29.0 kết quả theo số đo () = 263 và getTextBound (). Width = 260
slezica

Vui lòng gửi mã tối thiểu với thiết lập và đo lường Paint, có thể tái tạo vấn đề.
Con trỏ Null

Cập nhật. Xin lỗi lần nữa vì sự chậm trễ.
slezica

Tôi với Rich ở đây. Nghiên cứu tuyệt vời! Bounty hoàn toàn xứng đáng và được trao tặng. Cảm ơn bạn đã dành thời gian và nỗ lực của bạn!
slezica

16
@mice Tôi vừa tìm thấy câu hỏi này một lần nữa (Tôi là OP). Tôi đã quên mất câu trả lời của bạn đáng kinh ngạc như thế nào. Cảm ơn từ tương lai! 100rp tốt nhất tôi đã đầu tư!
slezica

21

Kinh nghiệm của tôi với điều này là getTextBoundssẽ trả về chỉnh lưu giới hạn tối thiểu tuyệt đối đó gói gọn văn bản, không nhất thiết phải là chiều rộng đo được sử dụng khi kết xuất. Tôi cũng muốn nói rằng measureTextgiả sử một dòng.

Để có được kết quả đo chính xác, bạn nên sử dụng StaticLayout để hiển thị văn bản và rút ra các phép đo.

Ví dụ:

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

Không biết về StaticLayout! getWidth () sẽ không hoạt động, nó chỉ trả về những gì tôi vượt qua trong trình xây dựng (nghĩa là widthkhông phải maxWidth), nhưng getLineWith () sẽ. Tôi cũng tìm thấy phương thức tĩnh getDesiredWidth (), mặc dù không có chiều cao tương đương. Hãy sửa câu trả lời! Ngoài ra, tôi thực sự muốn biết tại sao rất nhiều phương pháp khác nhau mang lại kết quả khác nhau.
slezica

Xấu của tôi về chiều rộng Nếu bạn muốn độ rộng của một số văn bản sẽ được hiển thị trên một dòng duy nhất, thì nó measureTextsẽ hoạt động tốt. Mặt khác, nếu bạn biết các hằng số chiều rộng (chẳng hạn như trong onMeasurehoặc onLayout, bạn có thể sử dụng giải pháp tôi đã đăng ở trên. Bạn có đang cố gắng tự động thay đổi kích thước văn bản không? , chỉ cần chỉnh lưu giới hạn nhỏ nhất cần thiết để vẽ.
Đuổi theo

Tôi thực sự đang cố gắng tự động thay đổi kích thước một TextView. Tôi cũng cần chiều cao, đó là nơi vấn đề của tôi bắt đầu! đoWidth () hoạt động tốt khác. Sau đó, tôi đã tò mò về lý do tại sao các cách tiếp cận khác nhau lại cho các giá trị khác nhau
slezica

1
Một điều cũng cần lưu ý là một textview bao bọc và là một multiline sẽ đo ở match_parent cho chiều rộng và không chỉ chiều rộng yêu cầu, điều này làm cho tham số chiều rộng ở trên thành StaticLayout hợp lệ. Nếu bạn cần một trình khôi phục văn bản, hãy xem bài đăng của tôi tại stackoverflow.com/questions/5033012/iêu
Đuổi theo

Tôi cần phải có một hình ảnh động mở rộng văn bản và điều này đã giúp một loạt! Cảm ơn bạn!
hộp

18

Câu trả lời của chuột rất tuyệt ... Và đây là mô tả về vấn đề thực sự:

Câu trả lời đơn giản ngắn gọn là Paint.getTextBounds(String text, int start, int end, Rect bounds)trả về Rectmà không bắt đầu (0,0). Nghĩa là, để có được chiều rộng thực tế của văn bản sẽ được đặt bằng cách gọi Canvas.drawText(String text, float x, float y, Paint paint)với cùng một đối tượng Paint từ getTextBound (), bạn nên thêm vị trí bên trái của Rect. Một cái gì đó như thế:

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;
}

Lưu ý điều này bounds.left - đây là chìa khóa của vấn đề.

Theo cách này, bạn sẽ nhận được cùng chiều rộng của văn bản mà bạn sẽ nhận được bằng cách sử dụng Canvas.drawText() .

Và chức năng tương tự sẽ được nhận heightvăn bản:

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: Tôi đã không kiểm tra mã chính xác này, nhưng đã kiểm tra việc thụ thai.


Giải thích chi tiết hơn nhiều được đưa ra trong câu trả lời này .


2
Rect định nghĩa (b. Thong) là (b.right-b.left) và (b.height) là (b.bottom-b.top). Do đó, bạn có thể thay thế (b.left + b. Thong) bằng (b.right) và (b.bottom + b.height) bằng (2 * b.bottom-b.top), nhưng tôi nghĩ bạn muốn có ( b.bottom-b.top) thay vào đó, giống như (b.height). Tôi nghĩ bạn đã đúng về chiều rộng (dựa trên việc kiểm tra nguồn C ++), getTextWidth () trả về cùng giá trị như (b.right), có thể có một số lỗi làm tròn. (b là một tham chiếu đến giới hạn)
Nulano

11

Xin lỗi vì đã trả lời lại câu hỏi đó ... Tôi cần phải nhúng hình ảnh.

Tôi nghĩ rằng kết quả @mice tìm thấy là sai. Các quan sát có thể đúng với kích thước phông chữ là 60 nhưng chúng khác nhau nhiều hơn khi văn bản nhỏ hơn. Ví dụ. 10px. Trong trường hợp đó, văn bản thực sự được vẽ BEYOND giới hạn.

nhập mô tả hình ảnh ở đây

Mã nguồn của ảnh chụp màn hình:

  @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, sương mù kéo về. Tôi vẫn quan tâm đến điều này, ngay cả khi tò mò. Hãy cho tôi biết nếu bạn tìm thấy bất cứ điều gì khác, xin vui lòng!
slezica

Vấn đề có thể được khắc phục một chút khi bạn nhân số phông chữ bạn muốn đo theo hệ số cho phép nói 20 và hơn là chia kết quả cho điều đó. THAN bạn cũng có thể muốn thêm 1-2% chiều rộng của kích thước đo chỉ để ở bên an toàn. Cuối cùng, bạn sẽ có văn bản được đo là dài nhưng ít nhất không có văn bản chồng chéo.
Moritz

6
Để vẽ văn bản chính xác bên trong chỉnh lưu giới hạn, bạn không được vẽ Văn bản có X = 0,0f, vì chỉnh lưu giới hạn đang trả về như trực tràng, không giống như chiều rộng và chiều cao. Để vẽ chính xác, bạn nên bù tọa độ X trên giá trị "-bound.left", sau đó nó sẽ được hiển thị chính xác.
La Mã

10

TUYÊN BỐ TỪ CHỐI: Giải pháp này không chính xác 100% về mặt xác định chiều rộng tối thiểu.

Tôi cũng đang tìm cách đo văn bản trên vải. Sau khi đọc bài viết tuyệt vời từ chuột, tôi có một số vấn đề về cách đo văn bản đa dòng. Không có cách rõ ràng từ những đóng góp này, nhưng sau một số nghiên cứu, tôi đã tìm thấy trên lớp StaticLayout. Nó cho phép bạn đo văn bản nhiều dòng (văn bản có "\ n") và định cấu hình nhiều thuộc tính hơn cho văn bản của bạn thông qua Paint liên quan.

Đây là đoạn trích cho biết cách đo văn bản nhiều dòng:

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;
}

Wrawitdh có thể xác định nếu bạn muốn giới hạn văn bản đa dòng của mình ở một chiều rộng nhất định.

Vì StaticLayout.getWidth () chỉ trả về giới hạn này, bạn phải thực hiện một bước khác để có được chiều rộng tối đa theo yêu cầu của văn bản nhiều dòng của bạn. Bạn có thể xác định chiều rộng của mỗi dòng và chiều rộng tối đa là chiều rộng dòng cao nhất của khóa học:

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;
}

bạn không nên chuyển ivào getLineWidth (bạn chỉ cần vượt qua 0 mỗi lần)
Rich S

2

Có một cách khác để đo giới hạn văn bản một cách chính xác, đầu tiên bạn nên lấy đường dẫn cho Paint và văn bản hiện tại. Trong trường hợp của bạn nên như thế này:

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

Sau đó, bạn có thể gọi:

mPath.computeBounds(mBoundsPath, true);

Trong mã của tôi, nó luôn trả về giá trị đúng và mong đợi. Nhưng, không chắc chắn nếu nó hoạt động nhanh hơn phương pháp của bạn.


Tôi thấy điều này hữu ích vì dù sao tôi cũng đang thực hiện getTextPath, để vẽ phác thảo nét xung quanh văn bản.
ToolmakerSteve

1

Sự khác biệt giữa getTextBoundsmeasureTextđược mô tả với hình ảnh dưới đây.

Nói ngắn gọn,

  1. getTextBoundslà để có được RECT của văn bản chính xác. Độ measureTextdài của văn bản, bao gồm khoảng cách thêm ở bên trái và bên phải.

  2. Nếu có khoảng trắng giữa văn bản, nó được đo bằng measureTextnhưng không bao gồm độ dài của TextBound, mặc dù tọa độ được dịch chuyển.

  3. Văn bản có thể nghiêng (Skew) trái. Trong trường hợp này, hộp giới hạn bên trái sẽ vượt ra ngoài phép đo của số đo và độ dài tổng thể của văn bản bị ràng buộc sẽ lớn hơnmeasureText

  4. Văn bản có thể nghiêng (Skew) phải. Trong trường hợp này, phía bên phải của hộp giới hạn sẽ vượt ra ngoài phép đo của số đo, và chiều dài tổng thể của văn bản bị ràng buộc sẽ lớn hơnmeasureText

nhập mô tả hình ảnh ở đây


0

Đây là cách tôi tính các kích thước thực cho chữ cái đầu tiên (bạn có thể thay đổi tiêu đề phương thức cho phù hợp với nhu cầu của mình, tức là thay vì char[]sử dụng 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;
}

Lưu ý rằng tôi đang sử dụng TextPaint thay vì lớp Paint ban đầu.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.