:Android font, 安卓字體全攻略-經驗觀點免费ppt模版下载-道格办公

Android font, 安卓字體全攻略

容等等有些疑問, 所以我很想了解一下安卓字體的全攻略。請問你有什麼具體的問題或者需要了解的方面嗎?

超詳細!安卓巴士開發者大會嘉賓及主題介紹

一直沒有詳細地去了解android字體的相關內容, 實際開發的時候總是對設計稿上面字體和其他控件的間距, 字體內部的行距很疑惑, 直接設置好像每次都差幾個像素, 簡直逼死強迫症患者.
今天我們就一起來看看, 字體的秘密.

字體結構

要想對攻略字體, 我們先了解清楚字體裡面都有些什麼.
在分析字體的時候, 我們基本只需要關注垂直方向, 如下圖

font結構

垂直方向有5條關鍵橫線.
綠色的橫線是最關鍵的基線(base line), 字體的位置都是相對於基線的, 所以從坐標的角度看, 基線就是y=0的坐標軸.
頂部(ascent)和底部(descent )的紅色橫線分別為字體的上下'邊界'.

注意, 雖然說是'邊界', 但是實際渲染字體的時候是可能會超過邊界的, 我個人理解這兩條線算是字體設計者在設計層面< /strong>給程序提供的一個參考值. 這兩條線在設計字體的時候是可以由設計者設置的.

基線到ascent的區域稱為升部(ascender), 即圖中右側紫色的區域.
基線到descent的區域稱為降部(descender), 即圖中右側藍色的區域.
黃色的虛線是主線(mean line), 決定無升部的小寫字母的高度, 例如e, z, c等. 這個高度又叫x字高(x-height ), 也就是圖中右側褐色的區域.
玫紅色的虛線大寫高度(cap height), 也就是圖中右側綠色的區域.

Em, UPM

除了上述基本結構, 我們還需要搞清楚Em的概念, 有些地方也叫UPM. 簡單地說, 字體設計者和程序之間需要有一個抽象的單位來描述字體的高度, 在金屬活字印刷時代就有Em來表示一個金屬塊的高度了, 所以也就沿用了以前Em的說法, 來表示字體的基本單位.

關鍵知識點: 在Android中, 設置text size的時候, 就是設置1Em的大小.

Em是由字體設計者在設計的時候自行決定將1Em劃分成多少份, 然後其他字體中的距離都是用相對Em的大小來描述的.

上面提到的很多值都是在字體設計的時候設置的, 顯然這些設置是保存在字體文件當中的, 而在Android中, 最常用的字體文件格式就是.ttf(True Type Font), 所以我們有必要稍微了解一下這種文件.

TTF(True Type Font)文件

TTF簡單地說就是一個標準, 用來統一字體的描述方式.
我們的目的不是為了設計字體, 只是希望搞清楚, 字體當中的設置是怎樣影響字體在Android TextView中的顯示的, 尤其想搞清楚如何根據字體文件計算垂直方向上字體佔用的空間.

注意, 接下來很多關於Ascent和Descent的結論都是通過代碼實測得到, 能力有限, 並沒有弄清楚其中的原理, 希望知道的朋友可以評論補充 :P

分析字體文件設置, 我們需要一個工具來查看這些.ttf文件, 我這裡用的是FontForge, 用這個軟件打開Android的默認字體Roboto Regular, 看看其中的字體信息.
打開文件後, 選擇Element ー> Font Info打開字體信息面板

FontForge

先看看General選項

Em信息

上圖可以看出, Roboto中, 把1Em分成了2048份,

實際上, 大部分ttf字體都是把1Em分成了2048份. 可能也有部分字體會分成4096份.

這裡還會看到Ascent和Descent的值, 不過經過實測, 這兩個並不是真正在Android中用到的Ascent和Descent.(我也很崩潰…這部分的資料很少, 並沒有深究這其中究竟有什麼不同)

真正在Android中的Ascent和Descent值需要看OS/2選項

字體信息面板

實測結論就是, 紅框中的這兩個值才是Android中的Ascent和Descent.

圖中頂部的Win Ascent和Win Descent是表示所有字中最高和最低的邊界, 但是這兩個值並不能對應上Android中的值, 原因不明…

在這圖中也能看到x-height和cap height的值.
那麼這個1900和-500是什麼意思呢?

像素計算

要計算字體的高度, 需要記住以下幾點:

  • 設置text size的時候是設置1Em的值

  • Roboto把1Em分成了2048份

  • 在Roboto中, Ascent為1900, Descent為-500

  • 在字體中, 基線(base line)是y=0的坐標軸
    根據1, 2兩點, 可以知道, 1份的值是(textSize / 2048) px, 假設text size是2048px, 那麼1份就是1px.
    而1900表示Ascent在基線上方, 距離是1900份. -500表示Descent在基線的下方, 距離是500份.
    所以理論上, 如果在字體的text size是2048px, 那麼對於這份Roboto Regular字體來說

ascender = 2048px / 2048 * 1900 = 1900px
// 同理
cap height = 1456px
x -height = 1082px
descender = 500px
總高度 = ascender + descender = 1900px + 500px = 2400px

隨便打開一個軟件, 使用Roboto Regular字體在文本框中輸入一段文字, 很容易就能驗證這個結論是正確的, 下圖是使用Sketch驗證的截圖

Sketch 2048px

基線為0, 左側可以看到各條線距離基線的距離, 右側可以看到文本框總高度為2400px, 和計算值一致.

那麼在Android的TextView中顯示是不是也是這樣呢?

Android TextView中的字體結構

在Android中實測得到的各個區域的值也是一致的, 但是字體的高度卻不等於TextView的高度, 如下圖

Android font結構

粉紅色就是TextView的背景色, 可以看到在Ascent和Descent之外分別還有一點距離才到TextView的邊緣, 也就是右側使用橙色方塊標出的fontPadding.

看到這個fontPadding, 不禁有幾點疑問

  • 這個fontPadding是什麼東西? 有什麼用?

  • 這兩個距離是由誰加上去的? 是字體設計者還是Android自己?

  • 還有我們最關心的問題, 這兩個距離的值怎麼計算?

我們一個一個問題來看.

font padding

設計字體的時候設置的Ascent和Descent我認為只是一個參考值, 因為世界上的除了字母和數字外還有其他一些字體,例如頂部有變音符的, 藝術字體這類需要佔用額外空間的字體, 所以font padding就是這個額外空間, 來確保所有字體都能顯示在區域內.

實際上, 上面提到的, ttf文件中的Win-Ascent和Win-Descent就是這個作用, 但是和Android中實際讀取到的值並不一致.

那麼這兩個值怎麼算? 我目前找到的辦法是通過代碼, 利用Paint#getFontMetrics獲取這兩個值.

FontMetrics

先簡單介紹下這個類, 包含了5個變量

  • top: 即上邊界, 因為在Android中, y軸正方向是向下的, 而基準線是y=0, 所以這個值是一個負數.

  • ascent: 字體文件中設置的Ascent值(即上文提到的在FontForge中查看到的HHead Ascent), 也是負數, 理由同上

  • descent: 字體文件中設置的Descent值(即上文提到的在FontForge中查看到的HHead Descent), 正數

  • bottom: 下邊界, 正數

  • leading: 兩行之間, 上一行的bottom和下一行的top的間距, 然而這個值總是0, 可以忽略.
    更具體的說明可以看看這個回答Meaning of top, ascent, baseline, descent, bottom, and leading in Android's FontMetrics

而頂部的font padding就是|top - ascent|, 底部的font padding就是bottom - ascent

我們來實測以下, 通過以下方法讀取字體的相關值

public  static void printFontMetrics(Context context, @ FontRes int fontRes, int emSize) {
    Paint paint = new Paint();
    // 設置字體, 使用兼容庫來通過font資源id獲取Typeface實例
    paint.setTypeface(ResourcesCompat.getFont(context, fontRes));
    // 把字體大小設置成em size方便查看
paint.setTextSize(emSize);
    FontMetrics metrics = paint.getFontMetrics();
    Log.d('metrics',
        'top = ' + metrics.top +
            ', ascent = ' + metrics.ascent +
', descent = ' + metrics.descent +
            ', bottom = ' + metrics.bottom +
            ', leading = ' + metrics.leading);
}

對於Rotobo Regular , 調用

// 從上面可以知道Rotobo Regular的em size是2048
printFontMetrics(context, R.font. roboto_regular, 2048);

輸出為

D/metrics: top = -2163.0, ascent = -1900.0, descent = 500.0, bottom = 555.0, leading = 0.0

ascentdescent的值和我們從FontForge中查看ttf文件得到的值一樣, 由於坐標系的不同, 符號相反.

但是topbottom我並沒有找到規律, 希望知道的朋友指教一下.

不過不影響結論, 當textSize=2048的時候, 上面的Android font結構圖中的fontPadding, 頂部的值是2163 - 1900 = 263, 底部的值是550 - 500 = 55, 可以自行截圖驗證, 得到以上值之後, 我們就可以通過計算得到字體的上下font padding了

// Rotobo Regular字體
topFontPadding = textSzie * (2163 - 1900) / 2048
bottomFontPadding = textSize * (550 - 500) / 2048
< /code>

同時還能知道字體的實際高度

// Rotobo Regular字體
height = textSize * (2163 + 550) / 2048 = textSize * 1.3247

那麼為什麼是由topbottom決定字體的高度的呢?那麼我們就要看TextView的實現了, 而對於普通的文本, 繪製是由android.text.BoringLayout負責的.

BoringLayout

決定文本高度的關鍵代碼在於init方法, 其實很簡單, 不看下面的代碼也沒關係

void  init(CharSequence source,
    TextPaint paint, int outerwidth,
    Alignment align,
    float spacingmult, float spacingadd,
    BoringLayout.Metrics metrics, boolean includepad,
    boolean trustWidth)
 
{
    int spacing;
    // 忽略非重點代碼
    // metrics雖然不是FontMetrics, 但含義一致
    // spacing就是字體單行所佔高度
    // mDesc就是字體的下邊界
    if (includepad) {
        spacing = metrics.bottom - metrics.top;
        mDesc = metrics.bottom;
    } else {
        spacing = metrics.descent - metrics.ascent;
        mDesc = metrics.descent;
    }

    mBottom = spacing;

    // 忽略非重點代碼
    / / 記錄上下font padding
    if (includepad) {
        mTopPadding = metrics.top - metrics.ascent;
        mBottomPadding = metrics .bottom - metrics.descent;
    }
}

邏輯很簡單, 關鍵在includepad, 這個值其實就是android:includeFontPadding的值, 這個值默認是true的, 所以默認情況下

Android中的字體高度是|bottom| + |top|, 而普通軟件(例如word, Sketch或者其他設計軟件)中, 字體高度使用的是|descent| + |ascent|, 所以Android中的字體在垂直方向上總是比設計稿的多佔一點空間.

分析到這裡, 解決方案也很明顯了

對於普通的字體, 要完美復刻設計稿的字體高度, 應該把android:includeFontPadding設置為false

當然你也可以手動計算這個font padding, 然後做偏移.
不過這個值默認為true是有原因的, 因為這個距離是為了保證
字體中所有'符號'都能顯示完全, 因此對於特殊的字體, 如果把這個值設為false, 有可能導致部分字母顯示不全, 例如Heavenly Font, 對比如下

Heavenly Font

右側是把android:includeFontPadding設置為false後的情況, 部分字母顯示不完整.
因此使用這個方法前先確定下字體的能夠正常顯示, 不過實際上大部分常規字體都不需要這個額外空間的, 大部分情況下還是能夠放心使用的.

注意, 對於指定的字體文件不支持的文字, 例如使用英文字體文件輸入中文, 樣式會使用系統默認字體的樣式, 但是空間計算的時候還是會按照指定的字體文件的參數來計算, 而不是默認字體的參數.

行距

行距就是相鄰兩行的基線之間的距離.

默認行距的實際值等於字體設置中的|Descent| + |Aescent|

例如對於Roboto Regular來說, textSize為2048px時, 行距為500 + |-1900| = 2400px

在Android的TextView中, 可以通過android:lineSpacingExtraandroid:lineSpacingMultiplier修改行距. 其中lineSpacingExtra默認值為0, < code >lineSpacingMultiplier默認值為1, 有以下公式

行距=默認行距 * lineSpacingMultiplier + lineSpacingExtra

希望大家看完, 都能了解清楚字體在Android中, 佔用高度的計算規則, 如有紕漏, 歡迎評論討論。

大家都在看

Flutter完整開發實戰詳解(三、 打包與填坑篇)

Android中高級面試題準備整理分享

Framework學習Android系統源碼下載與編譯

Android Dex分包之旅

歡迎前往安卓巴士博客區投稿,技術成長於分享

期待巴友留言< em >,共同探討學習

文章為用戶上傳,僅供非商業瀏覽。發布者:Lomu,轉轉請註明出處: https://www.daogebangong.com/zh-Hant/articles/detail/Android%20font%20a%20complete%20guide%20to%20Android%20fonts.html

(810)
打賞 支付宝扫一扫 支付宝扫一扫
single-end

相關推薦