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,然后我们去测量高度

<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 测量文本宽度

  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 的高度和文本高度

<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
  1. getMeasuredHeight()为控件整体高度
  2. getLayout().getHeight()为文本绘制高度
  3. FontMetrics和 TextBounds测量的结果都是一行的高度。即使我们内容设置很多,也只是宽度变多。
    如果需要测量多行 ,需要*行号+间距。以TextView 为例(非源码,源码请看 BoringLayout):

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容