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”時的顯示效果
設置android:lineSpacingExtra=”-6dp”時的顯示效果
設置android:lineSpacingExtra=”-10dp”時的顯示效果
設置不同的android:lineSpacingMultiplier顯示效果如下。
正常行間距下顯示效果
設置android:lineSpacingMultiplier=”1.2”時的顯示效果
設置android:lineSpacingMultiplier=”2.0”時的顯示效果
設置android:lineSpacingMultiplier=”0.5”時的顯示效果
設置android:lineSpacingMultiplier=”0”時的顯示效果(等於0時文字不可見)
行間距設置影響分析
對正常行間距,android:lineSpacingExtra=”2dp”,和android:lineSpacingMultiplier=”2”設置下的實際行間距進行測量,得到如下數值
正常行間距下,實際間距爲12px
+2dp行間距下,實際間距爲18px
2倍行間距下,實際間距爲64px
可以看到在android:lineSpacingExtra=”2dp”設置下,實際行間距也增加了6px(測試手機上1dp=3px),但在android:lineSpacingMultiplier=”2”設置下,實際行間距並不是正常行間距的兩倍。
進一步的,如果在測量時將文字高度也計算在內,得到如下數值:
正常行間距下,包含文字高度間距爲52px
+2dp行間距下,包含文字高度間距爲58px
2倍行間距下,包含文字高度間距爲104px(這裏測量使用的是第二行的間距,原因下文會描述)
通常意義下所說的行間距是指一行文字下方距離下一行文字上方的距離,行高是指一行文字上方距離下一行文字上方的距離。
通過對比可以看出,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()方法的註釋如下
/**
* @returnthe height of one standard line in pixels. **Note that markup
* within thetext can cause individual lines to be taller or shorter
* than this height, andthe layout may contain additional first-
* orlast-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;
publicvoidsetLineSpacing(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()源碼如下
publicintgetLineHeight() {
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。
booleanneedMultiply = (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的數值。這和代碼中得到的結論相同。