Android Textview获取文本高度及drawable 居中

1.文字基本构成

文字区域.png

getHeight() = Leading(一般为0 不考虑)+Ascent+Descent

2.安卓文本绘制

Android font结构.png

baseline绘制点.png

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会造成一些字体显示不完整一般不建议修改。


FontPadding.png

BoringLayout.png

定义一个 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 测量文本宽度

  1. measureText 测量
getPaint().measureText(getText().toString())
  1. textBounds 测量
Rect rect = new Rect();
getPaint().getTextBounds(getText().toString(), 0, getText().toString().length(), rect);
Log.e(TAG, "TextBound width = " + rect.width());
  1. 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
  1. getMeasuredHeight()为控件整体高度
  2. getLayout().getHeight()为文本绘制高度
  3. FontMetrics和 TextBounds测量的结果都是一行的高度。即使我们内容设置很多,也只是宽度变多。
    如果需要测量多行 ,需要*行号+间距。以TextView 为例(非源码,源码请看 BoringLayout):

(fm.descent - fm.ascent) * getLineCount()+getLineSpacingExtra() * (getLineCount()-1)
TextView 中有getLineSpacingMultiplier()和getLetterSpacing()我们用不到

你可能感兴趣的:(Android Textview获取文本高度及drawable 居中)