Android TextView行间距解析

来源:互联网 发布:linux acl 编辑:程序博客网 时间:2024/06/05 01:01

TextView行间距设置

在布局XML中有两个参数可以对TextView的行间距进行设置。
分别为:android:lineSpacingExtra 和 android:lineSpacingMultiplier。
在代码中可以通过TextView的setLineSpacing()方法来设置。

android:lineSpacingExtra

android:lineSpacingExtra表示额外的行间距数值,单位通常为dp。如android:lineSpacingExtra=”1dp”。
android:lineSpacingExtra的值可以为负数,小数和0。如果值为正数表示增加行间距,如果值为负数表示减少行间距,如果值为0,则没有变化。

android:lineSpacingMultiplier

android:lineSpacingMultiplier表示行间距的倍数,没有单位。如android:lineSpacingMultiplier=”1.2”。
android:lineSpacingMultiplier的值可以为任意浮点数。如果值大于1.0表示增加行间距,如果值小于1.0表示减少行间距。

android:lineSpacingExtra和android:lineSpacingMultiplier一起使用

android:lineSpacingExtra和android:lineSpacingMultiplier可以在一起对同一个TextView进行设置,同时使用时会先增加android:lineSpacingMultiplier设置的倍数,再加上android:lineSpacingExtra设置的额外的间距。

setLineSpacing()

setLineSpacing()原型为public void setLineSpacing(float add, float mult);
参数add表示要增加的间距数值,对应android:lineSpacingExtra参数。
参数mult表示要增加的间距倍数,对应android:lineSpacingMultiplier参数。

不同行间距设置下的显示效果

设置不同的android:lineSpacingExtra显示效果如下。

正常行间距下显示效果
正常行间距

设置android:lineSpacingExtra=”2”时的显示效果
2dp行间距

设置android:lineSpacingExtra=”-6”时的显示效果
-6dp行间距

设置android:lineSpacingExtra=”-10”时的显示效果
-10dp行间距

设置不同的android:lineSpacingMultiplier显示效果如下。
正常行间距下显示效果
正常行间距

设置android:lineSpacingMultiplier=”1.2”时的显示效果
1.2倍行间距

设置android:lineSpacingMultiplier=”2.0”时的显示效果
2.0倍行间距

设置android:lineSpacingMultiplier=”0.5”时的显示效果
0.5倍行间距

设置android:lineSpacingMultiplier=”0”时的显示效果(等于0时文字不可见)
0倍行间距

行间距设置影响分析

对正常行间距,android:lineSpacingExtra=”2dp”,和android:lineSpacingMultiplier=”2”设置下的实际行间距进行测量,得到如下数值
正常行间距下,实际间距为12px
正常行间距

+2dp行间距下,实际间距为18px
+2dp行间距

2倍行间距下,实际间距为64px
2倍行间距

可以看到在android:lineSpacingExtra=”2dp”设置下,实际行间距也增加了6px(测试手机上1dp=3px),但在android:lineSpacingMultiplier=”2”设置下,实际行间距并不是正常行间距的两倍。
进一步的,如果在测量时将文字高度也计算在内,得到如下数值:
正常行间距下,包含文字高度间距为52px
正常行间距

+2dp行间距下,包含文字高度间距为58px
+2dp行间距

2倍行间距下,包含文字高度间距为104px(这里测量使用的是第二行的间距,原因下文会描述)
2倍行间距

通常意义下所说的行间距是指一行文字下方距离下一行文字上方的距离,行高是指一行文字上方距离下一行文字上方的距离。
通过对比可以看出,android:lineSpacingExtra和android:lineSpacingMultiplier的设置并不是直接影响行间距,而是通过行高来影响行间距。当设置android:lineSpacingExtra=”2dp”时,行高增加2dp,文字高度不变,于是行间距增加2dp。当设置android:lineSpacingMultiplier=”2”时,行高增加两倍,文字高度不变,行间距增加(”文字高度”+”文字正常行间距”)

获取TextView行高

TextView提供了getLineHeight()方法来获取TextView的行高。
对正常行间距,android:lineSpacingExtra=”2dp”和android:lineSpacingMultiplier=”2”设置下通过getLineHeight()方法获取的行高如下。
正常行间距:52px
android:lineSpacingExtra=”2dp”:58px
android:lineSpacingMultiplier=”2”:104px
和实际测量值相同。

TextView行高和间距的异常情况

TextView实际显示的每行高度并不总是和getLineHeight()方法获取到的行高相等(这也意味着每行的间距也并不总是等于getLineHeight()方法获取到的行高减去文字高度)。
TextView getLineHeight()方法的注释如下

    /**     * @return the height of one standard line in pixels.  **Note that markup     * within the text can cause individual lines to be taller or shorter     * than this height, and the layout may contain additional first-     * or last-line padding.**     */

也就是说如果某行文字内有markup(不知道是什么),那么该行文字高度可能会大于,也可能会小于getLineHeight()方法返回的高度。此外对TextView的首行和最后一行有一个额外的padding间距,这导致实际行高要大于getLineHeight()方法得到的行高,实际间距也要大于通过getLineHeight()计算得到的间距。
对首行行高实际测量得到如下数值。
android:lineSpacingMultiplier=”1”时,“首行行高=普通行行高”
android:lineSpacingMultiplier=”2”时,“首行行高=普通行行高+6px”
android:lineSpacingMultiplier=”3”时,“首行行高=普通行行高+12px”
android:lineSpacingMultiplier=”4”时,“首行行高=普通行行高+18px”
也就是说“首行行高=普通行行高 + 6 ×(android:lineSpacingMultiplier - 1)”

TextView源码分析

要了解为何android:lineSpacingExtra和android:lineSpacingMultiplier设置影响的是行高而不是行间距,以及为何首行和最后一行的行高和普通行不相等,需要通过TextView源码来进行分析。
TextView 构造函数部分源码和setLineSpacing()源码如下

    case com.android.internal.R.styleable.TextView_lineSpacingExtra:        mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);        break;    case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:        mSpacingMult = a.getFloat(attr, mSpacingMult);        break;
public void setLineSpacing(float add, float mult) {        if (mSpacingAdd != add || mSpacingMult != mult) {            mSpacingAdd = add;            mSpacingMult = mult;            if (mLayout != null) {                nullLayouts();                requestLayout();                invalidate();            }        }    }

即android:lineSpacingExtra的设置保存在mSpacingAdd成员变量中,android:lineSpacingMultiplier的设置保存在mSpacingMult成员变量中。

TextView getLineHeight()源码如下

    public int getLineHeight() {        return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);    }

可以看到TextView计算行高就是将正常高度乘以mSpacingMult再加上mSpacingAdd得到的。
跟踪mSpacingAdd和mSpacingMult成员变量,发现在makeSingleLayout()方法中调用new StaticLayout构造方法时使用了这两个变量。在StaticLayout类的构造方法中调用了generate()方法,在generate()方法中又调用了out()方法,在out()方法中可以看到如下代码

if (needMultiply && !lastLine) {    double ex = (below - above) * (spacingmult - 1) + spacingadd;    if (ex >= 0) {        extra = (int)(ex + EXTRA_ROUNDING);    } else {        extra = -(int)(-ex + EXTRA_ROUNDING);    }} else {    extra = 0;}

其中needMultiply值定义为,即设置了android:lineSpacingExtra和android:lineSpacingMultiplier中任意一个,needMultiply的值就为true。

boolean needMultiply = (spacingmult != 1 || spacingadd != 0);

那么只要设置了android:lineSpacingExtra和android:lineSpacingMultiplier中任意一个,就会计算一个extra的值,这个extra的值等于(below - above) * (spacingmult - 1) + spacingadd向上取整后的值。
在这之后有如下代码

lines[off + TOP] = v;...v += (below - above) + extra;...lines[off + mColumns + TOP] = v;

跟踪lines[]相关代码可以看到,lines[]是mLines的引用,而mLines中保存了每行文字的垂直位置信息,每个普通行文字有三个位置信息,分别对应mLines中的三个元素(mColumns = COLUMNS_NORMAL = 3, START = 0, TOP = 1, DESCENT = 2)。
因此lines[off + mColumns + TOP]表示的是下一行的TOP位置信息,将lines[off + mColumns + TOP] - lines[off + TOP]就可以得到下一行的TOP位置距离本行的TOP位置距离,也就是行高。显然lines[off + mColumns + TOP] - lines[off + TOP] = (below - above) + extra。如果忽略extra计算时的取整过程,则lines[off + mColumns + TOP] - lines[off + TOP] = (below - above) + extra = (below - above) + (below - above) * (spacingmult - 1) + spacingadd = (below - above) * spacingmult + spacingadd。这就解释了为何android:lineSpacingExtra和android:lineSpacingMultiplier设置影响的是行高而不是行间距。
在out()方法往前可以看到如下代码。

if (firstLine) {    if (trackPad) {        mTopPadding = top - above;    }    if (includePad) {        above = top;    }}

跟踪includePad变量,可以看到是TextView的mIncludePad变量值,而TextView中mIncludePad固定设置为true,因此这里above = top;将执行,也就是说对首行文字,其above位置等于top位置,由此可以得出lines[off + mColumns + TOP] - lines[off + TOP] = (below - above) * spacingmult + spacingadd = (below - top) * spacingmult + spacingadd。也就是说首行文字的行高要比普通行高出(top - above) * spacingmult个像素。
注意到above = top的设定,首行文字在测量其行高时,其起始位置应当在文字top - above像素上方的空白位置。结合上文中首行行高的测量情况有如下数值。
android:lineSpacingMultiplier=”1”时,“首行行高=普通行行高+6px”
android:lineSpacingMultiplier=”2”时,“首行行高=普通行行高+12px”
android:lineSpacingMultiplier=”3”时,“首行行高=普通行行高+18px”
android:lineSpacingMultiplier=”4”时,“首行行高=普通行行高+24px”
也就是说“首行行高 - 普通行行高 = 6 ×(android:lineSpacingMultiplier)”这里的6显然就是top - above的数值。这和代码中得到的结论相同。

2 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 魅族手机应用加密忘记密码了怎么办 小米电视盒子3s死机黑屏怎么办 小米note充电充不进去电怎么办 乐视手机otg功能用不了怎么办 小米4c手机玩王者荣耀卡怎么办 捡的小米6被锁了怎么办 小米5s手机玩穿越火线卡怎么办 苹果平板更新完系统登录不上怎么办 谷歌商店一直卡在核对信息怎么办 贴吧邮箱忘了无法登陆怎么办 华为手机绑定了账号忘了怎么办 淘宝的手机多次注册无法登录怎么办 电脑看不到U盘里面的文件怎么办 c盘访问权限被锁了怎么办 xp系统电脑开机密码忘记了怎么办 华为荣耀9用久了卡怎么办 华为p9相机模糊敲打就正常怎么办 华为p9蓝频按什么都没反映怎么办 华为手机p9开机密码忘了怎么办 华为P9手机开机一直闪屏怎么办 荣耀畅玩7x用户数据锁定怎么办 华为手机p9的指纹解锁坏了怎么办 华为P9青春版外放音量小怎么办 华为麦芒5的开关键失灵怎么办 小米手机进水黑屏但是有声音怎么办 小米5手机突然黑屏没电怎么办 小米4开不了机怎么办充电没反应 小米手机充电没反应开不了机怎么办 红米note手机开不了机怎么办 红米手机突然黑屏开不了机怎么办 红米2a开不开机怎么办 红米4手开不了机怎么办 红米4a开不了机怎么办 魅族手机拨打电话时黑屏怎么办 金立手机拨打电话时黑屏怎么办 红米手机刷机黑屏了怎么办 酷派手机开机黑屏但能嗡嗡响怎么办 酷派手机忘记锁屏密码怎么办 酷派手机锁屏密码忘了怎么办 酷派手机不停的开机关机怎么办 苹果6手机进水了开不了机怎么办