我正在使用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
答案 0 :(得分:356)
你可以做我所做的检查这个问题:
研究Android源代码,Paint.java源代码,同时查看measureText和getTextBounds方法。 您将了解到measureText调用native_measureText,而getTextBounds调用nativeGetStringBounds,这是在C ++中实现的本机方法。
所以你继续研究Paint.cpp,它实现了两者。
native_measureText - &gt; SkPaintGlue :: measureText_CII
nativeGetStringBounds - &gt; SkPaintGlue :: getStringBounds
现在您的研究会检查这些方法的不同之处。 在一些参数检查之后,在Skia Lib(Android的一部分)中调用函数SkPaint :: measureText,但它们都调用不同的重载形式。
进一步深入Skia,我看到两个调用在同一个函数中导致相同的计算,只返回不同的结果。
回答您的问题: 你的两个调用都做同样的计算。结果的可能差异在于 getTextBounds 将边界作为整数返回,而 measureText 返回浮点值。
所以你得到的是在将float转换为int期间舍入错误,这发生在调用函数SkRect :: roundOut的SkPaintGlue :: doTextBounds中的Paint.cpp中。
这两个调用的计算宽度之间的差异可能最大为1。
编辑2011年10月4日
什么可能比可视化更好。我付出了努力,为了自己的探索,并为了值得赏金:)
这是字体大小60,红色是 bounds 矩形,紫色是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);
}
答案 1 :(得分:19)
我对此的体验是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();
答案 2 :(得分:16)
老鼠的答案很棒......这里是对真正问题的描述:
简单的简单回答是Paint.getTextBounds(String text, int start, int end, Rect bounds)
返回Rect
,而(0,0)
不会从Canvas.drawText(String text, float x, float y, Paint paint)
开始。也就是说,要获得将通过使用getTextBounds()中的相同 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;
}
来设置的文本的实际宽度,您应该添加Rect的左侧位置。这样的事情:
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;
}
:
{{1}}
P.s。:我没有测试这个确切的代码,但测试了这个概念。
在this回答中给出了更详细的解释。
答案 3 :(得分: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 );
}
}
答案 4 :(得分: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;
}
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;
}
答案 5 :(得分:2)
还有另一种精确测量文本边界的方法,首先应该获取当前Paint和文本的路径。 在你的情况下它应该是这样的:
p.getTextPath(someText, 0, someText.length(), 0.0f, 0.0f, mPath);
之后你可以打电话:
mPath.computeBounds(mBoundsPath, true);
在我的代码中,它总是返回正确的和期望的值。 但是,不确定它是否比你的方法更快。
答案 6 :(得分:1)
getTextBounds
和measureText
之间的区别如下图所示。
简而言之
getTextBounds
用于获取准确文本的RECT。 measureText
是文本的长度,包括左右的多余空格。
如果文本之间有空格,尽管坐标发生了偏移,但它以measureText
进行度量,但不包括TextBounds的长度。
文本可能会向左倾斜(倾斜)。在这种情况下,边框的左侧将超出measureText的度量范围,并且绑定文本的总长度将大于measureText
文本可能会向右倾斜(倾斜)。在这种情况下,边框的右侧将超出measureText的度量范围,并且绑定文本的总长度将大于measureText
答案 7 :(得分: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;
}
请注意,我使用的是TextPaint,而不是原始的Paint类。