Android Paint:.measureText() и getTextBounds()
Я измеряю текст с помощью 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
7 ответов:
вы можете сделать то, что я сделал, чтобы проверить такую проблему:
изучение исходного кода Android, краска.источник java, см. Оба метода measureText и getTextBounds. Вы узнаете, что measureText вызывает native_measureText, а getTextBounds вызывает nativeGetStringBounds, которые являются собственными методами, реализованными в C++.
таким образом, вы будете продолжать изучать краски.cpp, который реализует оба.
native_measureText -> SkPaintGlue:: measureText_CII
nativeGetStringBounds -> SkPaintGlue:: getStringBounds
теперь ваше исследование проверяет, где эти методы отличаются. После некоторых проверок param оба вызывают функцию SkPaint:: measureText в Skia Lib (часть Android), но они оба вызывают разные перегруженные формы.
копаясь дальше в Skia, я вижу, что оба вызова приводят к одному и тому же вычислению в одной и той же функции, только возвращают результат по-разному.
чтобы ответить на ваш вопрос: Оба ваших вызова делают одно и то же вычисление. Возможная разница результата заключается в том, что getTextBounds возвращает границы как целое число, в то время как measureText возвращает значение типа float.
Итак, что вы получаете, это ошибка округления при преобразовании float в int, и это происходит в Paint.cpp в SkPaintGlue::doTextBounds в вызове функции SkRect:: roundOut.
разница между вычисленной шириной этих двух вызовов может быть максимально 1.
EDIT 4 Oct 2011
что может быть лучше, чем визуализация. Я приложил усилия, для собственного изучения, и для заслуженной щедрости:)
это размер шрифта 60, красным цветом границы прямоугольник, в фиолетовый является результатом measureText.
видно, что левая часть границ начинается с нескольких пикселей слева, и значение measureText увеличивается на это значение как слева, так и справа. Это уже что-то называется значением AdvanceX глифа. (Я обнаружил это в источниках Skia в SkPaint.cpp)
таким образом, результат теста заключается в том, что 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); }
мой опыт заключается в том, что
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();
ответ мыши велик... А вот описание реальной проблемы:
короткий простой ответ заключается в том, что
Paint.getTextBounds(String text, int start, int end, Rect bounds)возвращаетRectкоторый не начинается с(0,0). То есть, чтобы получить фактическую ширину текста, которая будет установлена путем вызоваCanvas.drawText(String text, float x, float y, Paint paint)С то же самое Paint объект из getTextBounds () вы должны добавить левую позицию 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; }P. s.: Я не тестировал этот точный код, но проверил концепцию.
более подробное объяснение дано в этой ответ.
Извините, что снова отвечаю на этот вопрос... Мне нужно было вставить изображение.
Я думаю, что результаты @мыши найдены missleading. Наблюдения могут быть правильными для размера шрифта 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 ); } }
отказ от ответственности: данное решение не является 100% точным с точки зрения определения минимальной ширины.
Я также выяснял, как измерить текст на холсте. После прочтения Великого поста от мышей у меня были некоторые проблемы о том, как измерить многострочный текст. Нет очевидного способа из этих вкладов, но после некоторых исследований я cam через класс 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; }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( 0 ) > maxLine ) { maxLine = layout.getLineWidth( 0 ); } } return maxLine; }
есть еще один способ точно измерить границы текста, сначала вы должны получить путь для текущей краски и текста. В вашем случае это должно быть так:
p.getTextPath(someText, 0, someText.length(), 0.0f, 0.0f, mPath);после этого вы можете позвонить:
mPath.computeBounds(mBoundsPath, true);в моем коде он всегда возвращает правильные и ожидаемые значения. Но, не уверен, что это работает быстрее, чем ваш подход.
вот как я рассчитал реальные размеры для первой буквы (вы можете изменить заголовок метода в соответствии с вашими потребностями, т. е. вместо
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.

