TextView及其内部文字宽高测量

TextView及其内部文字宽高测量_第1张图片
请仔细阅读上图中的文字。
或 查看原文

测量中,有三种常见模式。

1、EXACTLY
当我们将控件的“layout_width”属性或者“layout_height”属性指定为具体数值时,比如“android:layout_width=“200dp””,或者指定为“match_parent”时,系统会使用这个模式。

2、AT_MOST
当控件的“layout_width”属性或者“layout_height”属性设置为“wrap_content”时,控件大小一般会随着内容的大小而变化,但是无论多大,也不能超过父控件的尺寸。

3、UNSPECIFIED
表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,一般在绘制自定义View的时候才会用到。

下面,是对各种情况下的测量,测试较多,请耐心看完

现在,有这样一个TextView

 

先进行“宽度”的测量
1、给TextView设置内容,

var str = "一行一行一行"
tv.text = str

常见的有2种宽度获取方式:通过API获取控件的宽;测量文字的长度。如下:

tv.measure(0, 0)
Log.e("宽度:", "${tv.measuredWidth}")
Log.e("高度", "${tv.measuredHeight}")

var textPaint: TextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
//单词大小是 20dp
textPaint.textSize = UiUtils.dp2px(this, 20f).toFloat()
Log.e("measureText ","${textPaint.measureText(str)}")

对应日志:

宽度:: 360
高度: 81
measureText: 360.0

这个360表示 360px,准确么?加个辅助线

 

效果图:
TextView及其内部文字宽高测量_第2张图片
由此可见,这样的结果,是准确的。

特别说明,在获取宽高前,一定要保证测量完成。可以使用一下三种方式的任意一种:

1、tv.measure(0, 0)
2、tv.post{...}
3、视图树

不可以直接去拿宽高,否则,很可能会拿到 0。因为,系统对控件的自动的测量、摆放、绘制,是在 onResume() 之后。
详见 View的绘制起点(读源码)笔记

以上,是 文字为一行的情况,如果,多行(自动折行),或者折行(代码控制折行)呢

var str = "一行一行一行\n2019年10月31日,天气,晴"
tv.text = str

tv.measure(0, 0)
Log.e("宽度:", "${tv.measuredWidth}")
Log.e("高度", "${tv.measuredHeight}")

var textPaint: TextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
//单词大小是 20dp
textPaint.textSize = UiUtils.dp2px(this, 20f).toFloat()
Log.e("measureText ","${textPaint.measureText(str)}")

对应日志:

宽度:: 752
高度: 160
measureText: 1131.0

设置一条 752px的辅助线。
TextView及其内部文字宽高测量_第3张图片
如果,折行的时候,里面有多行(自动换行)呢?
调整文字:

var str = "一行一行一行,一行一行一行,2019年10月31日\n天气,晴"

效果图:
TextView及其内部文字宽高测量_第4张图片
日志:

宽度:: 1292
高度: 160
measureText: 1551.0
screenWidth : 1080

我手机屏幕,宽度是 1080。

接下来,测试单纯的自动换行,再次调整

var str = "一行一行一行,一行一行一行,2019年10月31日,天气,晴"
tv.text = str

tv.measure(0, 0)
Log.e("宽度:", "${tv.measuredWidth}")
Log.e("高度", "${tv.measuredHeight}")

var textPaint: TextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
//单词大小是 20dp
textPaint.textSize = UiUtils.dp2px(this, 20f).toFloat()
Log.e("measureText ","${textPaint.measureText(str)}")
        
var screenWidth = UiUtils.getScreenWidth(this)

Log.e("screenWidth :","$screenWidth")

日志:

宽度:: 1592
高度: 81
measureText: 1592.0
screenWidth : 1080

结论:
获取宽度时,系统自动测量完成,或者手动通知后,可以拿到宽度(tv.measuredWidth),但是,仅限于 每一行的长度不超过屏幕宽度

接下来,是高度的相关测量。
上面测试多行的时候,高度值是错误的。下面会对各种测量方式做说明。

//行数
var lineNum = 1

var str = "2019年10月31日,天气,晴"
tv.text = str

 //第一种
tv.measure(0, 0)
Log.e("第一种测量方式", "${tv.measuredHeight}")

//第二种
var screenWidth = UiUtils.getScreenWidth(this)
var widthSpec = View.MeasureSpec.makeMeasureSpec(
		screenWidth,
		View.MeasureSpec.AT_MOST
	)
tv?.measure(widthSpec, 0)
Log.e("第 二 种测量方式的结果:", "${tv.measuredHeight}")

//第三种
var textPaint: TextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
//单词大小是 20dp
textPaint.textSize = UiUtils.dp2px(this, 20f).toFloat()
Log.e("第 三 种测量方式的结果:", "${(textPaint.descent() - textPaint.ascent()) * lineNum}")

//第四种
var mFontMetrics: Paint.FontMetrics = textPaint.fontMetrics

Log.e("第 四 种测量方式的结果 descent-ascent:", "${(mFontMetrics.descent - mFontMetrics.ascent) * lineNum}")
Log.e("第 四 种测量方式的结果 bottom-top:", "${(mFontMetrics.bottom - mFontMetrics.top) * lineNum}")

这里说明下 lineNum。为了简化,我下面,改变文字内容后,不再重复粘贴代码,改变文字后,行数是可以预知的,有多少行,就对应改变 lineNum 的值。可以计算出多行的高度

首先,还是从 单行开始。

var str = "2019年10月31日,天气,晴"
lineNum = 1

对应日志:

第 一 种测量方式: 81
第 二 种测量方式的结果:: 81
第 三 种测量方式的结果:: 70.3125
第 四 种测量方式的结果 descent-ascent:: 70.3125
第 四 种测量方式的结果 bottom-top:: 79.62891

现在,分别看下 70 和 80 (有81,有79.6)这2个位置



	


效果图:
TextView及其内部文字宽高测量_第5张图片

接下来,模拟换行的情况(手动换行,非自动换行),且每行都不超过屏幕(每行没有自动换行)

var str = "2019年10月31日,天气,晴\n2019年11月1日,天气,不知道"
var lineNum = 2

日志:

第 一 种测量方式: 160
第 二 种测量方式的结果:: 160
第 三 种测量方式的结果:: 140.625
第 四 种测量方式的结果 descent-ascent:: 140.625
第 四 种测量方式的结果 bottom-top:: 159.25781

只看数据,也能猜到结果了
TextView及其内部文字宽高测量_第6张图片
(红线160px,蓝线 141px)

接下来,是 手动换行中,有某一行的长度超过屏幕,造成了自动换行的情况。

var str = "2019年10月31日,天气,晴。今天,依旧没有进入富豪排行榜。\n2019年11月1日,天气,不知道"
 var lineNum = 3

日志

第 一 种测量方式: 160
第 二 种测量方式的结果:: 239
第 三 种测量方式的结果:: 210.9375
第 四 种测量方式的结果 descent-ascent:: 210.9375
第 四 种测量方式的结果 bottom-top:: 238.88672

TextView及其内部文字宽高测量_第7张图片
红线 239,蓝线 211

最后,看下单词的自动换行情况(文字中没有 \n ,只是文字超过屏幕宽度)

var str = "2019年10月31日,天气,晴。今天,依旧没有进入富豪排行榜。"
var lineNum = 2

日志:

第 一 种测量方式: 81
第 二 种测量方式的结果:: 160
第 三 种测量方式的结果:: 140.625
第 四 种测量方式的结果 descent-ascent:: 140.625
第 四 种测量方式的结果 bottom-top:: 159.25781

TextView及其内部文字宽高测量_第8张图片
红线 160,蓝线 141

结论:
1、descent - ascent,是文字的高度,不是文字在行的高度,mFontMetrics.bottom - mFontMetrics.top,是文字所在行的高度。
以单行为例,行高 = 文字高度 + 间隙。所以,用 文字高度作为 行高,是有误差的,但是,绘制文字的时候,可以直接用 descent、ascent

2、

tv.measure(0, 0)
Log.e("第 一 种测量方式", "${tv.measuredHeight}")

我个人的理解是,和 文字段数 有关。
如:一段文字,中间手动假如了“\n”换行,就是2段文字。那么,高度就是2行的高度。不管最后实际展示多少行。又或者,一段文字过长,哪怕自动换行了,拿到的,还是一行的高度

3、利用上面的第二种测量方法,可以获取到,完整的、准确的TextView的高度,适用于控件中已经填充了内容的情况。利用 第四种测量方式的 (bottom-top)*lineNum,可以预估计控件的高度(不能有行间距,有行间距,第四种办法,还有误差。见文章最后)。
如:有个弹框,需要预先估计下最大高度,可以用 第四种方法

最后,还有一种特殊情况:行间距。
现在,给TextView加上行间距

android:lineSpacingMultiplier="1.3"

设置内容

var str = "2019年10月31日,天气,晴\n2019年11月1日,天气,不知道"
var lineNum = 2

日志:

第 一 种测量方式: 184
第 二 种测量方式的结果:: 184
第 三 种测量方式的结果:: 140.625
第 四 种测量方式的结果 descent-ascent:: 140.625
第 四 种测量方式的结果 bottom-top:: 159.25781

现在,查看下 184,160,这2个位置
TextView及其内部文字宽高测量_第9张图片
红线 184,蓝线160。

你可能感兴趣的:(Android,Android笔记)