Android TextView行间距解析

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=”2dp”时的显示效果
2dp行间距

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

设置android:lineSpacingExtra=”-10dp”时的显示效果
-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的数值。这和代码中得到的结论相同。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页