Android Paint:.measureText()vs .getTextBounds()

sle*_*ica 188 android textview android-layout

我正在测量文本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())
    );
}
Run Code Online (Sandbox Code Playgroud)

输出显示的差异不仅得到大于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
Run Code Online (Sandbox Code Playgroud)

Poi*_*ull 364

你可以做我做的检查这样的问题:

研究Android源代码,Paint.java源代码,查看measureText和getTextBounds方法.您将了解到measureText调用native_measureText,而getTextBounds调用nativeGetStringBounds,这是在C++中实现的本机方法.

因此,您将继续研究Paint.cpp,它实现了两者.

native_measureText - > SkPaintGlue :: measureText_CII

nativeGetStringBounds - > SkPaintGlue :: getStringBounds

现在您的研究检查这些方法的不同之处.在一些参数检查之后,在Skia Lib(Android的一部分)中调用函数SkPaint :: measureText,但它们都调用不同的重载形式.

进一步深入Skia,我看到两个调用在同一个函数中导​​致相同的计算,只返回不同的结果.

回答你的问题: 你的两个电话都做同样的计算.结果的可能差异在于事实上getTextBounds将bounds返回为整数,而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);
  }
Run Code Online (Sandbox Code Playgroud)

  • @mice我刚刚再次发现这个问题(我是OP).我忘记了你的答案是多么令人难以置信.谢谢你的未来!我投资的最好的100rp! (16认同)
  • 很抱歉花了这么长时间评论,我有一个忙碌的一周.感谢您抽出宝贵时间深入了解C++代码!不幸的是,差异大于1,即使使用舍入值也会出现 - 例如,将文本大小设置为29.0会导致measureText()= 263和getTextBounds().width = 260 (2认同)

Cha*_*ase 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();
Run Code Online (Sandbox Code Playgroud)


Pri*_*off 18

老鼠的答案很棒......这里是对真正问题的描述:

简单的简单回答是没有开始的Paint.getTextBounds(String text, int start, int end, Rect bounds)回报.也就是说,要获得将通过使用getTextBounds()中的相同 Paint对象调用而设置的文本的实际宽度,您应该添加Rect的左侧位置.像这样的东西: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;
}
Run Code Online (Sandbox Code Playgroud)

注意这一点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;
}
Run Code Online (Sandbox Code Playgroud)

Ps:我没有测试这个确切的代码,但测试了这个概念.


这个答案中给出了更详细的解释.

  • Rect将(b.width)定义为(b.right-b.left),将(b.height)定义为(b.bottom-b.top).因此你可以用(2*b.bottom-b.top)代替(b.right)和(b.bottom + b.height)替换(b.left + b.width),但我认为你想要( b.bottom-b.top)相反,它与(b.height)相同.我认为你的宽度是正确的(基于检查C++源代码),getTextWidth()返回与(b.right)相同的值,可能会有一些舍入错误.(b是对边界的引用) (2认同)

Mor*_*itz 12

很抱歉再次回答这个问题...我需要嵌入图像.

我认为@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 );
    }
  }
Run Code Online (Sandbox Code Playgroud)

  • 要在边界矩形内准确绘制文本,您不能使用X = 0.0f绘制Text,因为bounding rect像rect一样返回,而不是像width和height一样.要正确绘制它,您应该偏移"-bounds.left"值上的X坐标,然后它将正确显示. (5认同)

Mor*_*itz 10

免责声明:该解决方案在确定最小宽度方面不是100%准确.

我还想弄清楚如何在画布上测量文本.在阅读了老鼠的精彩帖子后,我对如何测量多行文字有一些问题.从这些贡献中没有明显的方法,但经过一些研究,我突然发现了StaticLayout类.它允许您测量多行文本(带有"\n"的文本),并通过关联的Paint配置文本的更多属性.

以下是显示如何衡量多行文字的摘要:

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;
}
Run Code Online (Sandbox Code Playgroud)

wrapwitdh能够确定您是否要将多行文本限制为特定宽度.

由于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;
}
Run Code Online (Sandbox Code Playgroud)


Ely*_*lye 6

getTextBounds和之间的区别measureText用下图描述。

简而言之,

  1. getTextBounds是获取准确文本的 RECT。的measureText是文本的长度,包括左,右额外的差距。

  2. 如果文本之间有空格,它会被测量measureText但不包括在 TextBounds 的长度内,尽管坐标会移动。

  3. 文本可以向左倾斜(Skew)。在这种情况下,边界框左侧将超出 measureText 的测量范围,并且文本边界的总长度将大于measureText

  4. 文本可以向右倾斜 (Skew)。在这种情况下,边界框右侧将超出 measureText 的测量范围,并且文本边界的总长度将大于measureText

在此处输入图片说明