Android UI 文本還原紀實

對於 Android 開發同窗來講, "無論用戶比例多高, 設計稿都按 iOS 給"絕對是最值得吐槽的事情之一.android

在我剛開始接觸 Android 開發的那個階段, 每當有人問起這件事, 我都說 "Android 的作法就是看着差很少就好了..." 後來有些要求特別高的設計, Android 開發同窗就只能很苦逼的一個 dp 一個 dp 的改到 UI 滿意爲止. 我看如今很多輔助開發工具的思路也是這樣.git

17 年末 ~ 18 年初搞 UI 大改版的時候, iOS 開發同窗 傳人 Joe 跟設計敲定了 iOS 的還原方式. 我以爲若是 Android 不搞的話以後開發就太煩了, 就決定 試一試. 最後的方案雖然不是特別通用, 但也能解決大部分問題.github

而後就被設計小姐姐催着寫原理, 而後一年就過去了...bash


從這裏開始

圖: 未對齊時的樣子

上圖中, 粉底的 "22 一行"和 "22 多行"是設計小姐姐給的參照圖, 是用 Sketch 輸出的, 使用 22 號字+默認行高狀況下的設計稿上的樣子. 綠底和黃底的 "22 一行"是 Nexus 5 上, 使用 22dp 的樣子; 藍底(疊加顯示成紫色)的 "22 四行"也是 22dp, 文字用 "\n" 換行. 底色之間的差別就是"行高"的差別.工具

可見綠底一行的行高有一點細微的誤差, 黃底一行由於疊加了 4 次這個偏差, 比較明顯. 多行狀況下的偏差更大, 由於 Android 和 iOS 在多行文本排版的概念上差別很大. 參照圖上單行和多行是能對上的, 如今咱們要想辦法讓 Android 的單行和多行都能跟參照圖對上.post

單行對齊

觀察發現單行差的是底部的一段空白, 我稱之爲 additionalPaddingBottom. 對比各類字號的狀況, 發現並無規律, 所以搞出來一組經驗值.開發工具

這個經驗值在 3 倍屏上仍是比較準確的(最重要的是設計走查就用 3 倍屏...), 單行文字位置和行高都能對上. 在其餘倍數的屏幕上基本 ok, 但也有一些異常. 好比在 1.5 倍屏上, 部分字號只能達成行高對的上但文字位置對不上的效果, 並且還受到 setSingleLine 的影響, 15dp + setSingleLine(true) 時誤差尤爲大.字體

/**
 * density 爲 3 時的經驗值, 做爲計算 additionalLineSpace 的基數
 */
static {
    paddingBottomMap.put(10, 1f / 3);
    paddingBottomMap.put(11, 4f / 3);
    paddingBottomMap.put(12, 2f / 3);
    paddingBottomMap.put(13, 1f / 3);
    paddingBottomMap.put(14, 3f / 3);
    paddingBottomMap.put(15, 2f / 3);
    paddingBottomMap.put(16, 1f / 3);
    paddingBottomMap.put(17, 4f / 3);
    paddingBottomMap.put(19, 1f / 3);
    paddingBottomMap.put(22, 2f / 3);
    paddingBottomMap.put(30, 5f / 3);
}
複製代碼

多行對齊

根據 Android 文本排版概念, 我寫了個簡單的 MetricsTextView 來肯定單行和多行的行高關係:spa

Matrics

觀察發現: 兩行文字的高度 = 單行文字的高度 + 單行文字設置 setIncludeFontPadding(false) 的高度設計

同時, 兩行文字和兩組單行的差異在於文字之間的空白, 所以須要增長 lineSpaceExtra = topSpace + bottomSpace + additionalPaddingBottom. 這樣 Android 也實現了 n 行文字行高 = n x 單行文字行高, 多行也就對上了.

單行和多行對齊

行高

上面都是參考圖使用默認行高的狀況, 若是行高變了呢? 抱歉仍是得基於經驗值.

/**
 * sketch 中字號對應的默認行高 (dp)
 */
static {
    defaultLineHeightMap.put(10, 14);
    defaultLineHeightMap.put(11, 16);
    defaultLineHeightMap.put(12, 17);
    defaultLineHeightMap.put(13, 18);
    defaultLineHeightMap.put(14, 20);
    defaultLineHeightMap.put(15, 21);
    defaultLineHeightMap.put(16, 22);
    defaultLineHeightMap.put(17, 24);
    defaultLineHeightMap.put(19, 26);
    defaultLineHeightMap.put(30, 42);
}
複製代碼

首先咱們有默認行高的值, 而後把 deltaPaddingTop = deltaPaddingBottom = (lineHeight - defaultLineHeight) / 2 用 paddingTop 和 paddingBottom 加到 每一行 上 - 實驗結果代表上下加的同樣多, 能夠除 2, 真是幸運.

帶行高的對齊:

帶行高的對齊

侷限性

  • 只關注行高, 不關注文本寬度, 因此換行仍是跟 iOS 不同.
  • 並非對全部的字號/字體都有效, 只處理了咱們經常使用的字號(其它字號要加也不難), 默認字體.
  • 沒有抽成庫, 緣由就是上面那條.
  • 18 年 Android 最新的 support 庫好像爲 AppCompatTextView 增長了行高支持, 但我尚未來得及試.

其它已知問題

  • 5.0 如下系統須要特殊處理 paddingBottom, 由於會增長額外的 lineSpaceExtra
    • 可是有些 5.0 及以上的手機 (vivo X9, 錘子) 竟然也有這個問題, 管不了了...
  • 由於直接修改了 TextView 的 paddingTop 和 paddingBottom, 若是設計稿上有上下邊距, 只能用 margin 或者再嵌套一層的方式解決.
    • 也許能夠寫得更完善些.
  • 在生產環境中使用時, 有同窗發現該 TextView 中 ClickableSpan 的點擊事件沒法被觸發.
    • 還木有解決...

@Uraka.Lee

相關文章
相關標籤/搜索