Android Paint: .measureText() vs .getTextBounds()
You can do what I did to inspect such problem:
Study Android source code, Paint.java source, see both measureText and getTextBounds methods.
You'd learn that measureText calls native_measureText, and getTextBounds calls nativeGetStringBounds, which are native methods implemented in C++.
So you'd continue to study Paint.cpp, which implements both.
native_measureText -> SkPaintGlue::measureText_CII
nativeGetStringBounds -> SkPaintGlue::getStringBounds
Now your study checks where these methods differ.
After some param checks, both call function SkPaint::measureText in Skia Lib (part of Android), but they both call different overloaded form.
Digging further into Skia, I see that both calls result into same computation in same function, only return result differently.
To answer your question:
Both your calls do same computation. Possible difference of result lies in fact that getTextBounds returns bounds as integer, while measureText returns float value.
So what you get is rounding error during conversion of float to int, and this happens in Paint.cpp in SkPaintGlue::doTextBounds in call to function SkRect::roundOut.
The difference between computed width of those two calls may be maximally 1.
EDIT 4 Oct 2011
What may be better than visualization. I took the effort, for own exploring, and for deserving bounty :)
This is font size 60, in red is bounds rectangle, in purple is result of measureText.
It's seen that bounds left part starts some pixels from left, and value of measureText is incremented by this value on both left and right. This is something called Glyph's AdvanceX value. (I've discovered this in Skia sources in SkPaint.cpp)
So the outcome of the test is that measureText adds some advance value to the text on both sides, while getTextBounds computes minimal bounds where given text will fit.
Hope this result is useful to you.
Testing code:
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);
}
Android : Paint.getTextBounds returns width much greater than the width of the screen
The documentation states that int end is "1 past the last char in the string to measure." http://developer.android.com/reference/android/graphics/Paint.html#getTextBounds(java.lang.String, int, int, android.graphics.Rect)
You should have:
textView.getPaint().getTextBounds(text, 0, text.length(), rect);
This also applies to Canvas.drawText().
The TextView displaying text would be "wrapping" the text. The method getTextBounds is not aware of the TextView at all.
In fact, if you were to build your own custom View for displaying text, getTextBounds would be very useful for achieving your own text wrapping. The following code does a terrible job of presentation, but iterates over a String, drawing sections of it to a Canvas, effectively wrapping the text.
private void drawText(Canvas canvas) {
String text = "Nunc eleifend erat eu nulla varius iaculis. Quisque feugiat justo sit amet" +
" neque interdum tempor. Curabitur faucibus facilisis tristique. Donec posuere" +
" viverra magna, eu accumsan sapien congue id. Cras fringilla justo ut lacus" +
" molestie, et venenatis orci egestas. Vivamus pretium, nisl quis cursus cursus," +
" dolor eros porta neque, sit amet viverra ante orci non elit. Donec odio neque," +
" volutpat sit amet dui sit amet, fringilla tempus sapien. Donec sed mollis justo." +
" In dignissim tincidunt neque, eu luctus justo egestas nec. Donec commodo ut arcu" +
" vel placerat. Donec ullamcorper justo eget justo commodo hendrerit. Quisque" +
" commodo imperdiet posuere. Aenean vehicula dui.";
Paint paint = new Paint();
paint.setTextSize(30);
paint.setAntiAlias(true);
int padding = 20;
int availWidth = getWidth() - 2 * padding;
int availHeight = getHeight() - 2 * padding;
Rect bounds = new Rect();
int currentTextBottom = 0;
int firstCharInLine = 0;
int lastCharInLine = 0;
outerLoop: while (currentTextBottom < availHeight) {
while (Character.isWhitespace(text.charAt(firstCharInLine))) {
lastCharInLine = ++firstCharInLine;
}
for (int i = firstCharInLine + 1; i < text.length(); i++) {
paint.getTextBounds(text, firstCharInLine, i, bounds);
if (bounds.width() <= availWidth) {
if (Character.isWhitespace(text.charAt(i))) {
lastCharInLine = i;
}
} else {
currentTextBottom += bounds.height();
if (firstCharInLine == lastCharInLine) {
lastCharInLine = i;
}
canvas.drawText(text, firstCharInLine, lastCharInLine, padding, currentTextBottom + padding, paint);
firstCharInLine = lastCharInLine++;
break;
}
if (i == text.length() - 1) {
currentTextBottom += bounds.height();
lastCharInLine = text.length();
canvas.drawText(text, firstCharInLine, lastCharInLine, padding, currentTextBottom + padding, paint);
break outerLoop;
}
}
}
}
}
EDIT - as a side note, I've just now noticed a difference between bounds.width() and Paint.measureText()! Totally unrelated to the above code, I was having issues with Paint.Align.Center glitching and not always working as expected. So I removed any text alignment and tried:
canvas.drawText(
text,
viewWidth / 2f - bounds.width() / 2f,
viewHeight / 2f + bounds.height() / 2f,
paint);
But this doesn't align horizontally properly. When I did this instead:
float textWidth = paint.measureText(text);
canvas.drawText(
text,
viewWidth / 2f - textWidth / 2,
viewHeight / 2f + bounds.height() / 2f,
paint);
It aligned to my satisfaction :)
Android: measureText() Return Pixels Based on Scaled Pixels
You need to get the densityMultiplier like so:
final float densityMultiplier = getContext().getResources().getDisplayMetrics().density;
final float scaledPx = 20 * densityMultiplier;
paint.setTextSize(scaledPx);
final float size = paint.measureText("sample text");
Paint.measureText: Is the sum of the parts equal to the whole?
The whole is not always equal to the sum of the parts.
This appears to be a result of kerning and not related to any AdvanceX double padding that you mentioned.
Test code
Here is the test code:
String myString = "This is some text.";
Paint myPaint = new Paint();
myPaint.setAntiAlias(true);
myPaint.setTextSize(100);
float theWhole = myPaint.measureText(myString);
float sumOfTheParts = 0;
for (int i = 0; i < myString.length(); i++) {
sumOfTheParts += myPaint.measureText(myString, i, i + 1);
}
Results
In my first test, the measures are the same:
String myString = "This is some text.";
theWhole // 787.0
sumOfTheParts // 787.0
However, when there are letters that need kerning, the measures are different. This is the test again with a different string:
String myString = "AV Wa";
theWhole // 291.0
sumOfTheParts // 297.0
Implications
You cannot assume that measuring all the characters of a string and then summing the total will give you the actual length of the string. Thus, when doing text wrapping you will need to measure entire string ranges.
getTextBounds returning wrong values
The method takes the following parameters:
getTextBounds(char[] text, int index, int count, Rect bounds)
And you request the width of only one character (the third parameter), not the whole string:
paint.getTextBounds("ABCDEFGHI", 0, 1, bounds);
bounds.right
is 8, which is the width of the letter A
.
The correct call in your case would be:
String str = "ABCDEFGHI";
paint.getTextBounds(str, 0, str.length(), bounds);
android view width is not bigger when a11y is changed to big font
From the image I see that myAccountView expands up to the rounded edges. It's width spans to the rounded edges. But the text starts and ends after the end of left half circle and before the beginning of right half circle. So the padding of myAccountView should be considered. In your edit you considered the padding of myAccountView's parent which has no relevance.
While measuring the chipWidth the padding should be considered.
int chipWidth = myAccountView.getMeasuredWidth() - myAccountView.getPaddingLeft() - myAccountView.getPaddingRight();
How to quickly get width and height of TextView using Paint.getTextBounds()?
Found a simple and not slow method of determining text width/height drawn with specific Paint
that doesn't use StaticLayout
.
public int getTextWidth(String text, Paint paint) {
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
int width = bounds.left + bounds.width();
return width;
}
public int getTextHeight(String text, Paint paint) {
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
int height = bounds.bottom + bounds.height();
return height;
}
The short simple description of trick: Paint.getTextBounds(String text, int start, int end, Rect bounds)
returns Rect
which doesn't starts at (0,0)
. That is, to get actual width of text that will be set by calling Canvas.drawText(String text, float x, float y, Paint paint)
with the same Paint
object from getTextBounds()
you should add the left position of Rect
.
Notice this bounds.left
- this the key of the problem.
In this way you will receive the same width of text, that you would receive using Canvas.drawText()
.
Much more detailed explanation is given in this answer.
Related Topics
Importing Google-Play-Service Library Showing a Red X Next to This Reference Android
Listadapter Not Updating Item in Recyclerview
How to Center Text Horizontally and Vertically in a Textview
Find Distance Between Two Points on Map Using Google Map API V2
Android P - 'Sqlite: No Such Table Error' After Copying Database from Assets
How to Hide a Menu Item in the Actionbar
How to Implement a Long Click Listener on a Listview
How to Join Two SQLite Tables in My Android Application
How to Enable/Disable Bluetooth Programmatically in Android
Where Is Android.Os.Systemproperties
Web Colors in an Android Color Xml Resource File
Android Listview Not Refreshing After Notifydatasetchanged
How to Start Service Using Alarm Manager in Android
Formula Px to Dp, Dp to Px Android