Android 페인트 : .measureText () 및 .getTextBounds ()


191

나는 텍스트를 사용하여 측정하고있다. Paint.getTextBounds()렌더링 할 텍스트의 높이와 너비를 모두 얻는 데 관심이 있기 하고 있습니다. 그러나, 실제 텍스트 렌더링 항상보다 약간 넓은 .width()Rect의해 채워진 정보 getTextBounds().

놀랍게도, 나는 테스트 .measureText()했고, 다른 (더 높은) 값을 반환한다는 것을 발견했다. 나는 그것을 시도하고 그것이 맞는 것을 발견했다.

너비가 다른 이유는 무엇입니까? 높이와 너비를 올바르게 얻으려면 어떻게해야합니까? 나는 사용할 있습니다.measureText() 있지만에 .height()의해 반환 된 것을 신뢰 해야하는지 모르겠습니다 getTextBounds().

요청에 따라 다음은 문제를 재현하는 최소한의 코드입니다.

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

결과는 차이가 1보다 커질뿐만 아니라 마지막 순간 반올림 오류가 아니라 크기가 커지는 것처럼 보입니다 (더 많은 결론을 내리려고했지만 글꼴에 완전히 의존 할 수 있음).

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

[나의 길] [1]을 볼 수 있습니다. 그것은 위치와 올바른 경계를 얻을 수 있습니다. [1] : stackoverflow.com/a/19979937/1621354
Alex Chi

이 질문에 대한 다른 방문자의 텍스트 측정 방법에 대한 관련 설명 .
Suragch

답변:


370

그런 문제를 검사하기 위해 내가 한 일을 할 수 있습니다.

Android 소스 코드 인 Paint.java 소스를 연구하고 measureText 및 getTextBounds 메소드를 참조하십시오. measureText는 native_measureText를 호출하고 getTextBounds는 C ++로 구현 된 기본 메소드 인 nativeGetStringBounds를 호출한다는 것을 배웠습니다.

따라서 두 가지를 모두 구현하는 Paint.cpp를 계속 연구합니다.

native_measureText-> SkPaintGlue :: measureText_CII

nativeGetStringBounds-> SkPaintGlue :: getStringBounds

이제 연구는 이러한 방법이 다른 곳을 확인합니다. 일부 매개 변수 검사 후 Skia Lib (Android의 일부)에서 SkPaint :: measureText 함수를 호출하지만 둘 다 다른 오버로드 된 형식을 호출합니다.

Skia를 더 자세히 살펴보면 두 호출 모두 동일한 함수에서 동일한 계산으로 결과가 다르게 반환됩니다.

질문에 대답하려면 두 통화 모두 동일한 계산을 수행합니다. 결과의 가능한 차이는 getTextBounds 가 범위를 정수로 리턴 하는 반면 measureText 는 float 값 을 리턴 한다는 사실입니다 .

따라서 float을 int로 변환하는 동안 반올림 오류가 발생하며 SkRect :: roundOut 함수를 호출하는 SkPaintGlue :: doTextBounds의 Paint.cpp에서 발생합니다.

이 두 호출의 계산 너비 차이는 최대 1 일 수 있습니다.

2011 년 10 월 4 일 수정

시각화보다 낫다. 나는 자신의 탐험과 현상금을 얻기 위해 노력했습니다 :)

여기에 이미지 설명을 입력하십시오

이 글꼴 크기는 60이고 빨간색은 경계 사각형이며 자주색은 measureText의 결과입니다.

왼쪽 경계 부분은 왼쪽에서 일부 픽셀을 시작하며 measureText 값은 왼쪽과 오른쪽 모두에서이 값만큼 증가합니다. 이것은 Glyph의 AdvanceX 값이라고합니다. (나는 SkPaint.cpp의 Skia 소스에서 이것을 발견했습니다)

따라서 테스트 결과는 measureText가 양쪽 텍스트에 약간의 고급 값을 추가하는 반면 getTextBounds는 주어진 텍스트가 맞는 최소 경계를 계산합니다.

이 결과가 도움이 되길 바랍니다.

테스트 코드 :

  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
의견이 너무 오래 걸려서 죄송합니다. C ++ 코드를 파고 들어 주셔서 감사합니다! 불행히도 그 차이는 1보다 큽니다. 반올림 된 값을 사용하는 경우에도 발생합니다. 예를 들어 텍스트 크기를 29.0으로 설정하면 measureText () = 263 및 getTextBounds (). width = 260
slezica

페인트 설정 및 측정으로 문제를 재현 할 수있는 최소 코드를 게시하십시오.
Pointer Null

업데이트되었습니다. 지연해서 다시 죄송합니다.
slezica

저는 Rich와 함께 있습니다. 훌륭한 연구! 바운티는 충분히 가치가 있으며 수여되었습니다. 시간과 노력에 감사드립니다!
slezica

16
@mice 방금이 질문을 다시 발견했습니다 (OP입니다). 나는 당신의 대답이 얼마나 놀라운 지 잊었습니다. 미래에 감사합니다! 내가 투자 한 최고 100rpm!
slezica

21

이것에 대한 나의 경험은 getTextBounds렌더링 할 때 사용되는 측정 된 너비가 아니라 텍스트를 캡슐화하는 절대 최소 경계 직사각형을 반환 한다는 것입니다. 나도 말하고 싶어measureText 한 줄로 가정 .

정확한 측정 결과를 얻으려면 StaticLayout 하여 텍스트를 렌더링하고 측정 값을 가져와야합니다.

예를 들면 다음과 같습니다.

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에 대해 몰랐습니다! getWidth () 의지하지 작업, 그냥 내가 (이다 이는 costructor 전달 무엇을 반환 width하지 maxWidth), 그러나 getLineWith ()는 것이다. 또한 동등한 높이가 없지만 정적 메소드 getDesiredWidth ()를 발견했습니다. 답을 수정하십시오! 또한, 왜 그렇게 많은 다른 방법이 다른 결과를 산출하는지 알고 싶습니다.
slezica

폭에 관한 나의 나쁜. 한 줄에 렌더링 될 일부 텍스트의 너비를 원한다면 measureText제대로 작동해야합니다. 그렇지 않으면 너비 제한을 알고 있으면 (예 : in onMeasure또는 onLayout) 위에 게시 한 솔루션을 사용할 수 있습니다. 텍스트 퍼런 스의 크기를 자동으로 조정하려고합니까? 또한 텍스트 경계가 항상 작은 이유는 문자 패딩을 제외하기 때문입니다. , 그리기에 필요한 가장 작은 범위의 rect
Chase

실제로 TextView의 크기를 자동 조정하려고합니다. 나는 키도 필요합니다. 그것이 내 문제가 시작된 곳입니다! 그렇지 않으면 measureWidth () 잘 작동합니다. 그 후, 왜 다른 접근법이 다른 가치를 부여했는지 궁금해했습니다.
slezica

1
또한 한 줄로 감싸고 여러 줄로 된 텍스트 뷰는 필요한 너비뿐만 아니라 너비에 대해 match_parent에서 측정하므로 위의 너비 매개 변수를 StaticLayout에 적용합니다. 텍스트 크기 조정이 필요한 경우 stackoverflow.com/questions/5033012/
Chase

텍스트 확장 애니메이션이 필요했고 이것이 많은 도움이되었습니다! 감사합니다!
box

18

생쥐의 대답은 훌륭합니다 ... 그리고 실제 문제에 대한 설명은 다음과 같습니다.

간단한 대답은 에서 시작하지 않는 Paint.getTextBounds(String text, int start, int end, Rect bounds)리턴 Rect입니다 (0,0). 즉, getTextBounds ()에서 동일한 Paint 객체를 호출 Canvas.drawText(String text, float x, float y, Paint paint)하여 설정할 텍스트의 실제 너비를 얻으려면 Rect의 왼쪽 위치를 추가해야합니다. 그런 것 :

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

이것을 주목하십시오 bounds.left 문제의 핵심임을 .

이런 식으로 같은 너비의 텍스트를 받게됩니다. Canvas.drawText() .

그리고 height텍스트 를 얻는 데 동일한 기능이 있어야 합니다.

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

추신 : 나는이 정확한 코드를 테스트하지는 않았지만 개념을 테스트했습니다.


답변 에는 훨씬 더 자세한 설명이 나와 있습니다.


2
Rect는 (b.width)를 (b.right-b.left) 및 (b.height)를 (b.bottom-b.top)으로 정의합니다. 따라서 (b.left + b.width)를 (b.right)로, (b.bottom + b.height)를 (2 * b.bottom-b.top)으로 바꿀 수는 있지만 ( b.bottom-b.top) 대신 (b.height)와 동일합니다. 너비에 대해 정확하다고 생각합니다 (C ++ 소스 확인을 기반으로 함) getTextWidth ()는 (b.right)와 동일한 값을 반환하며 반올림 오류가있을 수 있습니다. (b는 경계에 대한 참조입니다)
Nulano

11

그 질문에 다시 대답해서 죄송합니다 ... 이미지를 포함해야했습니다.

@mice에서 찾은 결과가 잘못되었다고 생각합니다. 글꼴 크기가 60 인 경우 관측 값이 정확할 수 있지만 텍스트가 더 작 으면 훨씬 다르게 나타납니다. 예 : 10px. 이 경우 텍스트는 실제로 경계를 넘어서 그려집니다.

여기에 이미지 설명을 입력하십시오

스크린 샷의 소스 코드 :

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

아, 미쳤어. 호기심이 없어도 여전히 이것에 관심이 있습니다. 다른 것이 있으면 알려주세요!
slezica 2016 년

측정하려는 서체 크기에 20이라고하는 요소를 곱하고 결과를 그 수로 나누는 것보다 약간의 문제를 해결할 수 있습니다. 또한 안전한쪽에 있도록 측정 된 크기의 너비를 1-2 % 더 추가 할 수도 있습니다. 결국 당신은 긴 것으로 측정 된 텍스트를 가지게 될 것입니다. 그러나 최소한 겹치는 텍스트는 없습니다.
Moritz

6
경계 사각형 안에 정확하게 텍스트를 그리려면 경계 사각형이 너비 및 높이가 아닌 사각형으로 반환되므로 X = 0.0f로 텍스트를 그리면 안됩니다. 올바르게 그리려면 "-bounds.left"값에서 X 좌표를 오프셋해야합니다. 그러면 올바르게 표시됩니다.
로마

10

부인 성명: 이 솔루션은 최소 너비를 결정하는 관점에서 100 % 정확하지 않습니다.

또한 캔버스에서 텍스트를 측정하는 방법을 알아 냈습니다. 마우스에서 위대한 게시물을 읽은 후 여러 줄 텍스트를 측정하는 방법에 몇 가지 문제가있었습니다. 이러한 기여에서 분명한 방법은 없지만 몇 가지 연구를 마친 후에 StaticLayout 클래스를 캠으로 사용합니다. 여러 줄 텍스트 ( "\ n"가있는 텍스트)를 측정하고 연결된 페인트를 통해 훨씬 더 많은 텍스트 속성을 구성 할 수 있습니다.

여러 줄 문자를 측정하는 방법을 보여주는 스 니펫은 다음과 같습니다.

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

여러 줄 문자를 특정 너비로 ​​제한하려면 줄 바꿈을 결정할 수 있습니다.

StaticLayout.getWidth ()는이 boundedWidth 만 반환하므로 여러 줄 문자에 필요한 최대 너비를 얻으려면 다른 단계를 수행해야합니다. 각 선 너비를 결정할 수 있으며 최대 너비는 물론 가장 높은 선 너비입니다.

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

i당신은 getLineWidth 로 전달되어서는 안됩니다 (매번 0을 전달합니다)
Rich S

2

텍스트 경계를 정확하게 측정하는 다른 방법이 있습니다. 먼저 현재 페인트 및 텍스트의 경로를 가져와야합니다. 귀하의 경우 다음과 같아야합니다 :

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

그 후 당신은 전화 할 수 있습니다 :

mPath.computeBounds(mBoundsPath, true);

내 코드에서는 항상 정확하고 기대되는 값을 반환합니다. 그러나 접근 방식보다 빠르게 작동하는지 확실하지 않습니다.


텍스트 주위에 획 외곽선을 그리기 위해 어쨌든 getTextPath를 수행 한 이후로 유용하다는 것을 알았습니다.
ToolmakerSteve

2

다른 사이 getTextBounds와는 measureText아래의 이미지를 설명한다.

한마디로

  1. getTextBounds정확한 텍스트의 RECT를 얻는 것입니다. 는 measureText왼쪽과 오른쪽에 여분의 간격을 포함하여 텍스트의 길이입니다.

  2. 텍스트 사이에 공백이 있으면 measureText좌표는 이동하지만 TextBounds의 길이는 포함하지 않고 측정됩니다 .

  3. 텍스트가 기울어 질 수 있습니다 (Skew). 이 경우 왼쪽 경계 상자가 measureText의 측정 범위를 초과하고 텍스트 경계의 전체 길이가measureText

  4. 텍스트가 오른쪽으로 기울어 질 수 있습니다 (Skew). 이 경우 경계 상자 오른쪽이 measureText의 측정 범위를 초과하고 텍스트 경계의 전체 길이가measureText

여기에 이미지 설명을 입력하십시오


0

이것은 첫 글자의 실제 치수를 계산하는 방법입니다 (필요에 따라 메소드 헤더를 변경할 수 char[]있습니다 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;
}

원래 Paint 클래스 대신 TextPaint를 사용하고 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.