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

相关文章
相关标签/搜索