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,然后我们去测量高度
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="14sp"
android:text="测试" />
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 的高度和文本高度
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableTop="@drawable/ic_arrow_left"
android:gravity="center"
android:textSize="14sp"
android:text="测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试"
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()我们用不到