1.文字基本构成
getHeight() = Leading(一般为0 不考虑)+Ascent+Descent
2.安卓文本绘制
baseline是文本绘制的起始位置,向上为负数,向下为正数。
安卓提供了FontMetrics(),FontMetricsInt()用于文本的测量,而两个方法的区别就是返回值得类型。返回值一共五个属性:
- top: 即上边界, 因为在Android中, y轴正方向是向下的, 而基准线是y=0, 所以这个值是一个负数.
- ascent: 字体文件中设置的Ascent值(即上文提到的在FontForge中查看到的HHead Ascent), 也是负数, 理由同上
- descent: 字体文件中设置的Descent值(即上文提到的在FontForge中查看到的HHead Descent), 正数
- bottom: 下边界, 正数
- leading: 两行之间, 上一行的bottom和下一行的top的间距, 然而这个值总是0, 可以忽略.
这里的 FontPadding 要解释以下,fontPadding 在Textview中默认为 true 的,
可以通过 setIncludeFontPadding(boolean)方法修改。
当设置为 false后,BoringLayout中会判断使用FontMetrics的属性。
设置includeFontPadding 为 false会造成一些字体显示不完整一般不建议修改。
定义一个 TextView,textSize=14sp,然后我们去测量高度
Paint.FontMetrics fm = getPaint().getFontMetrics();
Log.e(TAG, "FontMetrics fm.top= " + fm.top);
Log.e(TAG, "FontMetrics fm.ascent= " + fm.ascent);
Log.e(TAG, "FontMetrics fm.descent = " + fm.descent);
Log.e(TAG, "FontMetrics fm.bottom= " + fm.bottom);
Log.e(TAG, "FontMetrics fm.leading= " + fm.leading);
FontMetrics fm.top= -44.3584
FontMetrics fm.ascent= -38.964844
FontMetrics fm.descent = 10.253906
FontMetrics fm.bottom= 11.381836
FontMetrics fm.leading= 0.0
安卓文本绘制是基于 baseline 开始的 ,baseline以上的为负数
3. 获取文本高度
3.1 通过getFontMetrics()获取
/**
* 获取字体绘制一行高度
* @return float
*/
private static float getHeight(@NonNull Paint paint) {
Paint.FontMetrics fm = paint.getFontMetrics();
return fm.descent - fm.ascent + fm.leading;
}
/**
* 获取字体绘制一行高度
* @return float
*/
private static float getHeightFontMetrics(@NonNull Paint paint) {
Paint.FontMetrics fm = paint.getFontMetrics();
return paint.getFontMetrics(fm);
}
/**
* 获取字体一行高度(包含文本上下间距)
* @return float
*/
private static float getHeightWithFontPadding(@NonNull Paint paint) {
Paint.FontMetrics fm = paint.getFontMetrics();
return fm.bottom - fm.top + fm.leading;
}
3.2 通过getTextBounds()获取
/**
* 获取字体一行高度
* @return int
*/
private static int getHeight(@NonNull Paint paint, @NonNull CharSequence str) {
Rect rect = new Rect();
paint.getTextBounds(str, 0, str.length(), rect);
return rect.height();
}
我们在来打印下日志
Paint.FontMetrics fm = getPaint().getFontMetrics();
Log.e(TAG, "FontMetrics fm.bottom - fm.top+ fm.leading = " + (fm.bottom - fm.top + fm.leading));
Log.e(TAG, "FontMetrics fm.descent - fm.ascent = " + (fm.descent - fm.ascent));
Log.e(TAG, "getPaint().getFontMetrics(fm)= " + getPaint().getFontMetrics(fm));
Rect rect = new Rect();
getPaint().getTextBounds(getText().toString(), 0, getText().toString().length(), rect);
Log.e(TAG, "TextBound height = " + rect.height());
FontMetrics fm.bottom - fm.top = 55.740234
FontMetrics fm.descent - fm.ascent = 49.21875
getPaint().getFontMetrics(fm)= 49.21875
TextBound height = 38
可以看出:
getPaint().getFontMetrics(fm))和fm.descent - fm.ascent 结果值是一样的
getHeight和FontMetrics高度不一致。
3.3 绘制高度和textview 高度不一致
Log.e(TAG, "真实绘制高度 getMeasuredHeight() = " + getMeasuredHeight());
Log.e(TAG, "文本绘制高度 getLayout().getHeight()= " + getLayout().getHeight());
真实绘制高度 getMeasuredHeight() = 57
文本绘制高度 getLayout().getHeight()= 57
是不是发现高度57>FontMetrics的高度>getTextBounds()的高度
这就是单独拎出来的原因,我看很多博客都是这样去测量高度,但是和 textview 高度确不一致,网上有人问 确没有人解答。
其实答案很简单,TextView 源码中是用的是FontMetricsInt,直接看代码
Paint.FontMetricsInt fmInt = getPaint().getFontMetricsInt();
Log.e(TAG, "测量高度 FontMetrics fmInt.bottom - fmInt.top = " + (fmInt.bottom - fmInt.top));
测量高度 FontMetrics fmInt.bottom - fmInt.top = 57
可以看出结果一样了,那么FontMetricsInt和FontMetrics的应用场景要怎么区分呢?
自定义 View通过 drawText()绘制的,使用FontMetrics或者 getTextBound()会更精准。
继承 TextView的扩展类可以使用FontMetricsInt获取高度。
3.4 测量文本宽度
- measureText 测量
getPaint().measureText(getText().toString())
- textBounds 测量
Rect rect = new Rect();
getPaint().getTextBounds(getText().toString(), 0, getText().toString().length(), rect);
Log.e(TAG, "TextBound width = " + rect.width());
- Layout 测量
Layout.getDesiredWidth(CharSequence source, TextPaint paint)
Layout.getDesiredWidth(CharSequence source, int start, int end, TextPaint paint)
我们设置一个宽度撑满,text 为"测试"的textview 看下 log
Log.e(TAG, "getLayout().getWidth() = " + getLayout().getWidth());
Log.e(TAG, "Layout.getDesiredWidth(getText(),getPaint()) = " + Layout.getDesiredWidth(getText(), getPaint()));
Log.e(TAG, "Layout.getDesiredWidth(getText(), 0, getText().length(), getPaint()) = " + Layout.getDesiredWidth(getText(), 0, getText().length(), getPaint()));
Log.e(TAG, "getPaint().measureText(getText().toString()) = " + getPaint().measureText(getText().toString()));
Rect rect = new Rect();
getPaint().getTextBounds(getText().toString(), 0, getText().toString().length(), rect);
Log.e(TAG, "TextBound width = " + rect.width());
控件真实宽度 getMeasuredWidth() = 1080
getLayout().getWidth() = 1080
Layout.getDesiredWidth(getText(),getPaint()) = 84.0
Layout.getDesiredWidth(getText(), 0, getText().length(), getPaint()) = 84.0
getPaint().measureText(getText().toString()) = 84.0
TextBound width = 81
基本上我们使用 measureText 就可以获取到文本的内容宽度。
我们设置多行文本,看看测量的宽度是什么样的。
控件真实宽度 getMeasuredWidth() = 1080
getLayout().getWidth() = 1080
Layout.getDesiredWidth(getText(),getPaint()) = 1596.0
Layout.getDesiredWidth(getText(), 0, getText().length(), getPaint()) = 1596.0
getPaint().measureText(getText().toString()) = 1596.0
TextBound width = 1593
结果很明显,控件宽度就1080px,但是测量出来的文本宽度超出了控件控件的宽度。这是因为测量都是基于一行来测量的。
如果需要判断一行可以显示多少字符可以使用
int endPos = layout.getLineEnd(maxLines - 1);
如果想自动换行的布局可以查看StaticLayout、DynamicLayout、BoringLayout
3.4 测量多行高度
我们修改xml 的文本为2行的内容,并设置 drawableTop,看下整体 view 的高度和文本高度
Log.e(TAG, "真实绘制高度 getMeasuredHeight() = " + getMeasuredHeight());
Log.e(TAG, "文本绘制高度 getLayout().getHeight()= " + getLayout().getHeight());
Log.e(TAG, "测量高度 FontMetricsInt fmInt.bottom - fmInt.top= " + (fmInt.bottom - fmInt.top));
Log.e(TAG, "测量高度 FontMetricsInt fmInt.descent - fmInt.ascent = " + (fmInt.descent - fmInt.ascent));
49
Rect rect = new Rect();
getPaint().getTextBounds(getText().toString(), 0, getText().toString().length(), rect);
Log.e(TAG, "TextBound height = " + rect.height());
真实绘制高度 getMeasuredHeight() = 163
文本绘制高度 getLayout().getHeight()= 112
测量高度 FontMetricsInt fmInt.bottom - fmInt.top=57
测量高度 FontMetricsInt fmInt.descent - fmInt.ascent=49
TextBound height = 38
- getMeasuredHeight()为控件整体高度
- getLayout().getHeight()为文本绘制高度
- FontMetrics和 TextBounds测量的结果都是一行的高度。即使我们内容设置很多,也只是宽度变多。
如果需要测量多行 ,需要*行号+间距。以TextView 为例(非源码,源码请看 BoringLayout):
(fm.descent - fm.ascent) * getLineCount()+getLineSpacingExtra() * (getLineCount()-1)
TextView 中有getLineSpacingMultiplier()和getLetterSpacing()我们用不到