如何簡單優雅的適配textview行間距?

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"導讀:TextView的行間距在不同設備下的一致性表現不盡如人意,這給視覺review帶來了不少麻煩,降低了RD&UI的工作效率,本文將探索出了一套低風險高兼容性的解決方案。該方案能夠完全統一TextView的行間距,保證了TextView行間距在不同機型上的一致性體驗,這極大程度減少了TextView相關的視覺聯調時間,提高了大家的工作效率。"}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"背景"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"視覺體驗也是各種手機APP產品質量不能忽視的一環。目前市場上手機型號多種多樣,各自屏幕的分辨率層出不窮,如何能在不同分辨率的屏幕上呈現出始終如一的顯示效果,也是每一位同學想要了解的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Android的屏幕碎片化嚴重,各種屏幕分辨率層出不窮,而在不同分辨率的屏幕上顯示出一致的效果,是百度App的研發團隊和視覺團隊共同追求的目標。在百度App的Android開發中,TextView的行間距屏幕適配問題在研發和視覺之間糾纏已久。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/f5\/f532c360f42b0b471a09b0a88acb5d32.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"該圖爲熱議頁面的圖文模板在三款設備上的顯示效果。可以看到TextView的行間距在三款設備下的一致性表現不盡如人意,而這已成爲日常UI開發以及視覺review過程中的一大痛點,降低了大家的工作效率。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面將探索一種簡單優雅的的TextView行間距適配方案。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先來分析下TextView在不同設備上行間距表現不一致的原因。百度App的UI團隊使用Sketch工具來進行UI設計以及UI review,因此本文接下來字體尺寸的測量都藉助Sketch工具完成。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先看下面一個簡單的xml佈局:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"將這段代碼運行在不同分辨率的機型上,藉助Sketch工具測量出各機型的行間距如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/f5\/f55d1675d696bf836879b437d48dd3bf.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從圖中可看出,同樣的字號大小,在分辨率爲720設備上,行間距測量結果爲5px;在分辨率爲1080設備上,行間距測量結果爲9px。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來修改下字號,將textSize改成24dp,並且看一下Mate20的效果:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在同一款設備(Mate 20)上,不同的字號,行間距的測量結果,如下圖所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/86\/86807dc1b02b0cd4554365b0979e95e1.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從圖中可看到,在同樣的設備上,不同的字號,行間距的測量結果也不一樣。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"具體表現爲:字號越大,行間距越大。這就讓人非常苦惱了,因爲一旦字號發生了變化,行間距就受到影響,行間距必須得跟隨字號重新調整,無形之中就增加了額外的工作量。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"讀到這大家可能會有疑問:XML佈局中並沒設置lineSpacingExtra \/ lineSpacingMultiplier屬性,那麼上面所測量的行間距是哪來的呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是因爲視覺對行間距的定義和Android系統對行間距的定義不一致導致的。視覺層面定義行間距非常簡單:即使用Sketch工具在上下相鄰的兩行文字中輸入大小相同的文字,同時畫出文字的矩形框,矩形框的高度爲文字的大小,比如在1080P,density=3的設計圖中,文字大小爲16dp,那麼矩形框的高度就設爲48px。上下兩個矩形框的間距就爲文字的行間距,這從上面的測量效果圖也可看出。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也就是說,即使沒有設置lineSpacingExtra \/ lineSpacingMultiplier屬性,但從視覺的角度來講,仍存在一定的行間距。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼在沒有設置lineSpacingExtra \/ lineSpacingMultiplier屬性的情況下,視覺所測量出來的行間距是什麼原因導致的?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面結合TextView源碼詳細分析下,首先看下圖:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/f3\/f32e20c37e1c84f2ed0affb263a37fdd.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"該圖展示了一行文字的繪製所需要的關鍵座標信息,圖中的幾根線表示字體的度量信息,在源碼中與其相對應的類爲FontMetrics.java,代碼如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\/**\n * Class that describes the various metrics for a font at a given text size.\n * Remember, Y values increase going down, so those values will be positive,\n * and values that measure distances going up will be negative. This class\n * is returned by getFontMetrics().\n *\/\npublic static class FontMetrics {\n \/**\n * The maximum distance above the baseline for the tallest glyph in\n * the font at a given text size.\n *\/\n public float top;\n \/**\n * The recommended distance above the baseline for singled spaced text.\n *\/\n public float ascent;\n \/**\n * The recommended distance below the baseline for singled spaced text.\n *\/\n public float descent;\n \/**\n * The maximum distance below the baseline for the lowest glyph in\n * the font at a given text size.\n *\/\n public float bottom;\n \/**\n * The recommended additional space to add between lines of text.\n *\/\n public float leading;\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代碼中對字體度量信息的每個字段含義的解釋非常詳細,大家看註釋即可,就不再過多解釋。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TextView對每行文字座標信息的計算細節是在StaticLayout.java類中的out()方法完成的,代碼如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"private int out(final CharSequence text, final int start, final int end, int above, int below,\n int top, int bottom, int v, final float spacingmult, final float spacingadd,\n final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,\n final boolean hasTab, final int hyphenEdit, final boolean needMultiply,\n @NonNull final MeasuredParagraph measured,\n final int bufEnd, final boolean includePad, final boolean trackPad,\n final boolean addLastLineLineSpacing, final char[] chs,\n final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,\n final float textWidth, final TextPaint paint, final boolean moreChars) {\n final int j = mLineCount;\n \/\/ 偏移量,標識當前的行號\n final int off = j * mColumns;\n final int want = off + mColumns + TOP;\n \/\/ 一維數組,保存了TextView各行文字的計算出來的座標信息。\n int[] lines = mLines;\n final int dir = measured.getParagraphDir();\n \n \/\/ 將所有的字體的度量信息存入fm變量中,然後通過LineHeightSpan接口將fm變量傳遞出去.\n \/\/ 這就給外部提供了一個接口去修改字體的度量信息。\n if (chooseHt != null) {\n fm.ascent = above;\n fm.descent = below;\n fm.top = top;\n fm.bottom = bottom;\n\n for (int i = 0; i < chooseHt.length; i++) {\n if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {\n ((LineHeightSpan.WithDensity) chooseHt[i])\n .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);\n } else {\n chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);\n }\n }\n \/\/ 獲取修改後的字體度量屬性\n above = fm.ascent;\n below = fm.descent;\n top = fm.top;\n bottom = fm.bottom;\n }\n \n if (firstLine) {\n if (trackPad) {\n mTopPadding = top - above;\n }\n\n if (includePad) {\n \/\/ 如果當前行是TextView的第一行文字,above(ascent)值使用top替代。\n above = top;\n }\n }\n\n int extra;\n\n if (lastLine) {\n if (trackPad) {\n mBottomPadding = bottom - below;\n }\n\n if (includePad) {\n \/\/ 如果當前行是TextView的最後一行文字,below(descent)值使用bottom替代。\n below = bottom;\n }\n }\n\n if (needMultiply && (addLastLineLineSpacing || !lastLine)) {\n \/\/ 計算行間距\n \/\/ spacingmult變量對應lineSpacingMultiplier屬性配置的值\n \/\/ spacingadd變量對應lineSpacingExtra屬性配置的值。\n double ex = (below - above) * (spacingmult - 1) + spacingadd;\n\n if (ex >= 0) {\n extra = (int)(ex + EXTRA_ROUNDING);\n } else {\n extra = -(int)(-ex + EXTRA_ROUNDING);\n }\n } else {\n extra = 0;\n }\n\n \/\/ 將當前行的座標信息存入mLines[]數組中\n lines[off + START] = start;\n lines[off + TOP] = v;\n lines[off + DESCENT] = below + extra;\n lines[off + EXTRA] = extra;\n\n \/\/ 計算下一行的的top值\n v += (below - above) + extra;\n \n mLineCount++;\n return v;\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於篇幅原因,省略了一些無關代碼。上面對關鍵代碼都給出了詳細的註釋,這裏就不過多解釋。通過第87行,可得出如下兩個公式:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"top座標計算:下一行Top值 = 本行Top值 + 行高"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"行高計算(排除第一行和最後一行):行高 = descent - ascent + 行間距 (descent值爲正,ascent值爲負)"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了方便大家理解行高,我把每行文字的baseline和top這兩根線畫了出來,紅色的線是baseline基線,綠色的線是top線,相鄰兩條綠線之間的距離即爲行高,如下圖所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/fe\/fe5c58ad1269ee8f8608d30cc1cf9567.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"到這裏,基本能夠解釋,在沒有設置lineSpacingExtra \/ lineSpacingMultiplier屬性的情況下,Sketch工具量出的行間距原因:我們知道每行文字以baseline作爲基線來繪製,在ascent範圍內繪製基線以上的部分,在descent範圍內繪製基線以下部分。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於漢字不會像英文那樣高低不一,是非常整齊的方塊字。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而漢字在descent範圍內繪製基線以下部分時,並沒有佔滿descent所有空間,會空出一部分距離,在ascent範圍內繪製基線以上部分時,也是同樣的道理。所以,Sketch測量出來的行間距就是上一行漢字佔據的descent範圍後的剩餘空間加上下一行漢字佔據的ascent範圍後的剩餘空間。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"適配方案"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經過上面分析,瞭解到TextView的自帶行間距是由於繪製的漢字沒有佔滿descent和ascent的空間引起的,且該行間距在不同的字號以及分辨率下表現不一。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"若能夠去除掉這部分行間距,就能達到適配目的。怎麼去除呢?我們再看一下系統TextView和視覺對一行文字行高的定義:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TextView:行高 = descent - ascent (descent值爲正,ascent值爲負)"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"視覺:行高 = 字體大小 (比如16dp的文字,行高=48px"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"只要能夠修改TextView的默認行高,讓其和視覺定義的行高保持統一,就能去除掉這部分行間距。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"怎麼修改TextView的默認行高呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其實TextView在設計的時候,提供了一個接口去修改TextView的行高。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回到上面對TextView的源碼分析,第20行-第39行,將字體的度量信息存入fm變量中,然後通過LineHeightSpan接口將fm變量傳遞出去,我們藉助這個LineHeightSpan就可以修改TextView的行高。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最終適配方案如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"public class ExcludeInnerLineSpaceSpan implements LineHeightSpan {\n \/\/ TextView行高\n private final int mHeight;\n\n public ExcludeInnerPaddingSpan(int height) {\n mHeight = height;\n }\n\n @Override\n public void chooseHeight(CharSequence text, int start, int end,\n int spanstartv, int lineHeight,\n Paint.FontMetricsInt fm) {\n \/\/ 原始行高\n final int originHeight = fm.descent - fm.ascent;\n if (originHeight <= 0) {\n return;\n }\n \/\/ 計算比例值\n final float ratio = mHeight * 1.0f \/ originHeight;\n \/\/ 根據最新行高,修改descent\n fm.descent = Math.round(fm.descent * ratio);\n \/\/ 根據最新行高,修改ascent\n fm.ascent = fm.descent - mHeight;\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"類ExcludeInnerLineSpaceSpan實現LineHeightSpan接口,這個類用於去除TextView的自帶行間距。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第5行,構造函數,以最新的行高作爲參數傳入。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第14行,計算出原始行高。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第19行,計算出新行高和原始行高的比例值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第21行-第23行,根據比例值修改字體度量的ascent參數和descent參數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來自定義個TextView出來,提供一個setCustomText()方法出來,供使用方調用。代碼如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"public class ETextView extends TextView {\n \/**\n * 排除每行文字間的padding\n *\n * @param text\n *\/\n public void setCustomText(CharSequence text) {\n if (text == null) {\n return;\n }\n\n \/\/ 獲得視覺定義的每行文字的行高\n int lineHeight = (int) getTextSize();\n\n SpannableStringBuilder ssb ;\n if (text instanceof SpannableStringBuilder) {\n ssb = (SpannableStringBuilder) text;\n \/\/ 設置LineHeightSpan\n ssb.setSpan(new ExcludeInnerLineSpaceSpan(lineHeight),\n 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n } else {\n ssb = new SpannableStringBuilder(text);\n \/\/ 設置LineHeightSpan\n ssb.setSpan(new ExcludeInnerLineSpaceSpan(lineHeight),\n 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n }\n\n \/\/ 調用系統setText()方法\n setText(ssb);\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"ShowCase"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"該方案使用系統公開API,簡單,侵入性低。並已在百度App熱議頁面上線,適配效果前後對比,如下圖所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/74\/7451a3ca57aa56b464555cce92a2027f.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/ad\/ad3f81262c8dcf2715f11ccb7dcfa78a.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"帶給用戶優雅簡單的視覺體驗,也是同學們不懈追求產品質量的體現。看完文章後,你是不是對手機TextView行間距適配方案有了新的啓發?歡迎大家與作者探討心得~"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"本文轉載自:百度架構師(ID:gh_38e0c590bf34)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"原文鏈接:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/mp.weixin.qq.com\/s\/E3J-SJEHacBqOqlwqFZu9Q","title":"xxx","type":null},"content":[{"type":"text","text":"https:\/\/mp.weixin.qq.com\/s\/E3J-SJEHacBqOqlwqFZu9Q"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章