與動(dòng)態(tài)設(shè)置字體大小實(shí)戰(zhàn))
1. 字體適配為什么成了Android開(kāi)發(fā)里的“隱形地雷”做過(guò)Android應(yīng)用的人大概都有過(guò)這種經(jīng)歷在測(cè)試機(jī)上跑得好好的界面到了用戶手里就變成了“車禍現(xiàn)場(chǎng)”——要么文字大得溢出屏幕要么小得看不清要么中英文混排時(shí)行高突然塌陷。更讓人頭疼的是這類問(wèn)題往往在開(kāi)發(fā)階段很難復(fù)現(xiàn)因?yàn)榇蠖鄶?shù)開(kāi)發(fā)者用的都是默認(rèn)字體縮放比例的測(cè)試機(jī)。字體適配這件事表面上看只是setTextSize()里傳個(gè)數(shù)值那么簡(jiǎn)單但真正踩過(guò)坑的人都知道它牽扯到系統(tǒng)設(shè)置、資源配置、自定義View、第三方庫(kù)、多語(yǔ)言切換等一連串環(huán)節(jié)。尤其是當(dāng)產(chǎn)品經(jīng)理提出“我們的應(yīng)用不要跟隨系統(tǒng)字體大小變化”或者“某些頁(yè)面要?jiǎng)討B(tài)調(diào)整字號(hào)”這類需求時(shí)如果沒(méi)有提前搞清楚底層機(jī)制后面就會(huì)陷入反復(fù)改、反復(fù)錯(cuò)的循環(huán)。這篇文章面向的是有一定Android開(kāi)發(fā)基礎(chǔ)、正在被字體適配問(wèn)題困擾的開(kāi)發(fā)者。我會(huì)從系統(tǒng)字體縮放的底層邏輯講起把“不跟隨系統(tǒng)字體大小”和“動(dòng)態(tài)設(shè)置字體大小”這兩個(gè)高頻需求拆開(kāi)揉碎結(jié)合我自己在多個(gè)項(xiàng)目中踩過(guò)的坑給出可直接落地的方案。文章里涉及的所有代碼和配置都經(jīng)過(guò)實(shí)際驗(yàn)證你可以直接拿去用但更重要的是理解每一步背后的原因這樣遇到變體需求時(shí)才不會(huì)慌。2. 系統(tǒng)字體縮放到底改了什么從Configuration到TextView的完整鏈路2.1 fontScale的存儲(chǔ)與生效時(shí)機(jī)Android系統(tǒng)在“設(shè)置-顯示-字體大小”里提供的調(diào)節(jié)選項(xiàng)本質(zhì)上修改的是Configuration對(duì)象中的fontScale字段。這個(gè)值默認(rèn)是1.0用戶每調(diào)大一檔它就會(huì)按預(yù)設(shè)的倍數(shù)增長(zhǎng)比如1.15、1.3、1.5等。系統(tǒng)把這個(gè)值持久化在Settings.System.FONT_SCALE里當(dāng)用戶修改后會(huì)觸發(fā)全局的配置變更。關(guān)鍵點(diǎn)在于這個(gè)配置變更會(huì)重啟當(dāng)前應(yīng)用的Activity除非你聲明了android:configChangesfontScale。重啟之后系統(tǒng)會(huì)重新創(chuàng)建Resources對(duì)象而Resources在解析sp單位時(shí)就會(huì)用fontScale去乘以基準(zhǔn)值。這就是為什么你用sp設(shè)置的字體會(huì)跟著系統(tǒng)變而用dp設(shè)置的不會(huì)——因?yàn)閐p的換算只依賴density跟fontScale沒(méi)關(guān)系。理解這條鏈路非常重要因?yàn)樗鼪Q定了你后續(xù)所有適配方案的邊界。你能控制的環(huán)節(jié)主要有三個(gè)一是讓Activity不重啟、自己處理配置變更二是攔截Resources的字體縮放三是在代碼里動(dòng)態(tài)計(jì)算字號(hào)。這三條路各有適用場(chǎng)景也各有各的坑。2.2 sp與dp的本質(zhì)區(qū)別以及為什么不能簡(jiǎn)單粗暴全用dp很多人第一次遇到字體適配問(wèn)題得到的建議就是“把sp全換成dp”。這個(gè)做法確實(shí)能讓字體不跟隨系統(tǒng)變化但它帶來(lái)的副作用同樣明顯用戶如果確實(shí)需要大字體比如視力不佳的老年用戶你的應(yīng)用就完全無(wú)視了他們的系統(tǒng)偏好這在無(wú)障礙體驗(yàn)上是不合格的。sp的設(shè)計(jì)初衷就是“scale-independent pixels”它存在的意義就是尊重用戶的字體大小選擇。所以正確的思路不是一刀切地棄用sp而是分場(chǎng)景決策哪些界面必須保持布局穩(wěn)定比如強(qiáng)交互的儀表盤(pán)、游戲HUD哪些界面應(yīng)該尊重用戶偏好比如閱讀類正文。對(duì)于前者我們可以用dp或者自定義的字體縮放策略對(duì)于后者保留sp反而是更友好的做法。這里還有一個(gè)容易被忽略的細(xì)節(jié)TextView的setTextSize()方法默認(rèn)接收的是sp單位。如果你傳入一個(gè)純數(shù)字比如setTextSize(16)它內(nèi)部會(huì)調(diào)用setTextSize(COMPLEX_UNIT_SP, 16)也就是說(shuō)它依然會(huì)受fontScale影響。想要用dp必須顯式寫(xiě)成setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16)。這個(gè)細(xì)節(jié)我在早期項(xiàng)目中就栽過(guò)跟頭當(dāng)時(shí)以為用代碼設(shè)置就能繞開(kāi)系統(tǒng)縮放結(jié)果測(cè)試時(shí)發(fā)現(xiàn)根本沒(méi)生效。2.3 Configuration變更引發(fā)的連鎖反應(yīng)當(dāng)用戶調(diào)整系統(tǒng)字體大小后如果Activity沒(méi)有聲明處理fontScale變更系統(tǒng)會(huì)銷毀并重建Activity。這個(gè)過(guò)程會(huì)帶來(lái)幾個(gè)連鎖問(wèn)題一是頁(yè)面狀態(tài)丟失如果沒(méi)做好onSaveInstanceState用戶填了一半的表單就沒(méi)了二是如果應(yīng)用里有單例持有Context重建后可能引用到舊的Resources導(dǎo)致字體顯示不一致三是如果用了自定義View在構(gòu)造函數(shù)里讀取的字體尺寸可能還是舊值。我在一個(gè)項(xiàng)目里就遇到過(guò)這種情況用戶調(diào)整字體后主界面正常刷新了但一個(gè)常駐通知里的字體還是舊的。排查后發(fā)現(xiàn)通知的RemoteViews是在Service里構(gòu)建的而Service并沒(méi)有隨Activity重建它持有的Resources還是舊的。解決辦法是在Service里監(jiān)聽(tīng)配置變化或者每次構(gòu)建通知時(shí)重新獲取當(dāng)前配置。3. 讓?xiě)?yīng)用不跟隨系統(tǒng)字體大小的幾種方案與取舍3.1 覆寫(xiě)attachBaseContext與applyOverrideConfiguration最直接也最常用的方案是在BaseActivity里覆寫(xiě)attachBaseContext創(chuàng)建一個(gè)新的Configuration把fontScale強(qiáng)制設(shè)為1.0然后用這個(gè)配置去創(chuàng)建Context。代碼大致如下Override protected void attachBaseContext(Context newBase) { Configuration config new Configuration(newBase.getResources().getConfiguration()); config.fontScale 1.0f; Context context newBase.createConfigurationContext(config); super.attachBaseContext(context); }這段代碼的核心邏輯是在Activity的Context被附加之前替換掉它的Configuration。這樣后續(xù)所有通過(guò)這個(gè)Context獲取的Resources在解析sp時(shí)都會(huì)用1.0的fontScale字體自然就不跟隨系統(tǒng)了。但這里有幾個(gè)坑必須注意。第一createConfigurationContext是API 17才有的方法如果你的minSdk低于17需要做版本判斷低版本可以用反射修改Resources的Configuration。第二這個(gè)方法只對(duì)通過(guò)該Context獲取的資源生效如果你在Application里初始化了某些全局的Resources那些地方不受影響。第三如果你用了androidx.appcompat庫(kù)AppCompatActivity本身也覆寫(xiě)了attachBaseContext你需要確保調(diào)用順序正確否則可能被覆蓋。3.2 在Application級(jí)別統(tǒng)一處理如果希望整個(gè)應(yīng)用包括Service、BroadcastReceiver等組件都不跟隨系統(tǒng)字體只在Activity里處理是不夠的。更徹底的做法是在自定義Application里覆寫(xiě)onConfigurationChanged或者在attachBaseContext里做全局替換。不過(guò)這里要特別小心Application的attachBaseContext只會(huì)被調(diào)用一次之后系統(tǒng)配置變化時(shí)不會(huì)再次觸發(fā)。所以如果你在Application里固定了fontScale用戶后續(xù)調(diào)整系統(tǒng)字體時(shí)應(yīng)用內(nèi)的字體不會(huì)實(shí)時(shí)變化——這恰好是“不跟隨”的需求但如果你希望部分頁(yè)面跟隨、部分不跟隨就不能在Application級(jí)別一刀切。我的建議是如果產(chǎn)品明確要求全局不跟隨就在Application里統(tǒng)一處理如果只是某些頁(yè)面不跟隨就在對(duì)應(yīng)的BaseActivity里處理。不要混用否則容易出現(xiàn)“有的頁(yè)面跟、有的不跟”的混亂局面。3.3 自定義TextView與字體縮放攔截還有一種更細(xì)粒度的方案自定義一個(gè)TextView在setTextSize里做攔截或者重寫(xiě)getTextSize的返回值。這種方案適合只需要對(duì)特定控件做處理的場(chǎng)景比如某個(gè)展示數(shù)字的儀表盤(pán)。但我不太推薦大規(guī)模使用這種方式原因有二一是侵入性強(qiáng)每個(gè)需要控制的控件都要替換成自定義View維護(hù)成本高二是容易遺漏比如Button、EditText這些間接繼承自TextView的控件如果忘記替換就會(huì)出現(xiàn)不一致。相比之下在Context層面做統(tǒng)一處理影響面更可控代碼也更集中。當(dāng)然如果你的項(xiàng)目已經(jīng)大量使用了自定義View那在自定義View的基類里統(tǒng)一處理也是可行的。3.4 方案對(duì)比與選型建議方案生效范圍實(shí)時(shí)性侵入性適用場(chǎng)景BaseActivity覆寫(xiě)attachBaseContext單個(gè)Activity及其Fragment重建后生效低部分頁(yè)面不跟隨Application全局處理整個(gè)應(yīng)用重建后生效低全局不跟隨自定義TextView攔截特定控件即時(shí)高個(gè)別控件特殊處理全用dp替代sp布局文件層面即時(shí)中布局穩(wěn)定性優(yōu)先選型時(shí)先問(wèn)自己三個(gè)問(wèn)題需求是全局還是局部是否需要尊重用戶的無(wú)障礙偏好項(xiàng)目里有沒(méi)有大量第三方庫(kù)的TextView需要統(tǒng)一處理想清楚這三點(diǎn)方案基本就定了。4. 動(dòng)態(tài)設(shè)置字體大小的那些坑從setTextSize到全局刷新4.1 setTextSize的單位陷阱前面提過(guò)setTextSize(float size)默認(rèn)用sp單位。但很多人不知道的是setTextSize(int unit, float size)里的unit參數(shù)如果傳TypedValue.COMPLEX_UNIT_PX那么size就是像素值不會(huì)再經(jīng)過(guò)任何縮放。這個(gè)特性在需要精確控制像素尺寸的場(chǎng)景下很有用比如自定義繪制。我遇到過(guò)一個(gè)典型案例某個(gè)頁(yè)面需要根據(jù)屏幕寬度動(dòng)態(tài)計(jì)算字號(hào)讓文字剛好占滿一行。如果用sp計(jì)算出來(lái)的值會(huì)被fontScale二次縮放導(dǎo)致實(shí)際顯示比預(yù)期大或小。正確的做法是用COMPLEX_UNIT_PX自己根據(jù)density和fontScale算出最終的像素值然后直接設(shè)置。float px TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, resources.getDisplayMetrics()); textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, px);這段代碼看起來(lái)繞但它把“sp轉(zhuǎn)px”的過(guò)程顯式化了你可以在中間插入自己的縮放邏輯比如乘以一個(gè)自定義的比例系數(shù)。4.2 動(dòng)態(tài)調(diào)整后布局不刷新的問(wèn)題動(dòng)態(tài)設(shè)置字體大小后最常見(jiàn)的現(xiàn)象是文字變了但控件的大小沒(méi)變導(dǎo)致文字被截?cái)嗷蛘咧車舭走^(guò)多。這是因?yàn)門(mén)extView的setTextSize只會(huì)觸發(fā)重繪不會(huì)自動(dòng)重新測(cè)量布局。如果父布局用的是wrap_content通常沒(méi)問(wèn)題但如果父布局是固定高度或者ConstraintLayout里用了固定尺寸約束就需要手動(dòng)調(diào)用requestLayout()。更麻煩的是RecyclerView里的場(chǎng)景。你動(dòng)態(tài)調(diào)整了某個(gè)Item的字體但I(xiàn)tem的高度是固定的文字就會(huì)顯示不全。這時(shí)候要么在Adapter里根據(jù)字體大小重新計(jì)算Item高度要么用DiffUtil觸發(fā)局部刷新。我在一個(gè)聊天列表里就遇到過(guò)這個(gè)問(wèn)題用戶調(diào)整字體后氣泡里的文字溢出了但氣泡背景沒(méi)跟著變。最后是在onBindViewHolder里根據(jù)當(dāng)前字體大小重新設(shè)置氣泡的LayoutParams才解決。4.3 多語(yǔ)言切換與字體大小的疊加效應(yīng)當(dāng)應(yīng)用支持多語(yǔ)言時(shí)字體適配會(huì)變得更復(fù)雜。不同語(yǔ)言的字符寬度差異很大比如同樣字號(hào)下中文和阿拉伯語(yǔ)的顯示寬度完全不同。如果再加上系統(tǒng)字體縮放計(jì)算量會(huì)成倍增加。我的經(jīng)驗(yàn)是對(duì)于多語(yǔ)言應(yīng)用不要試圖用一套固定的字號(hào)適配所有語(yǔ)言。更合理的做法是在資源文件里為每種語(yǔ)言定義不同的dimen值然后在代碼里根據(jù)當(dāng)前語(yǔ)言和字體縮放比例做二次調(diào)整。比如!-- values/dimens.xml -- dimen nametext_body16sp/dimen !-- values-ar/dimens.xml -- dimen nametext_body14sp/dimen這樣至少能保證基礎(chǔ)字號(hào)是合理的剩下的交給系統(tǒng)的fontScale去處理。如果產(chǎn)品要求不跟隨系統(tǒng)那就在Context層面統(tǒng)一攔截多語(yǔ)言的字號(hào)差異通過(guò)資源文件解決。4.4 第三方庫(kù)控件的字體適配項(xiàng)目里用了第三方UI庫(kù)的話字體適配的難度會(huì)再上一個(gè)臺(tái)階。因?yàn)榈谌綆?kù)的TextView往往有自己的默認(rèn)字號(hào)而且不一定暴露修改接口。比如某些彈窗庫(kù)、圖表庫(kù)它們的字號(hào)是寫(xiě)死在代碼里的。對(duì)于這種情況我通常用兩種辦法一是通過(guò)主題Theme覆蓋在styles.xml里定義android:textSize的默認(rèn)值很多庫(kù)會(huì)讀取主題屬性二是用LayoutInflater.Factory2在View創(chuàng)建時(shí)攔截動(dòng)態(tài)替換字號(hào)。第二種方式比較重但對(duì)付那些頑固的庫(kù)很有效。getLayoutInflater().setFactory2(new LayoutInflater.Factory2() { Override public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { View view getDelegate().createView(parent, name, context, attrs); if (view instanceof TextView) { // 在這里統(tǒng)一調(diào)整字號(hào) } return view; } });這段代碼要慎用因?yàn)樗鼤?huì)影響整個(gè)Activity的View創(chuàng)建過(guò)程稍有不慎就會(huì)引發(fā)性能問(wèn)題或兼容性問(wèn)題。建議只在確實(shí)沒(méi)有其他辦法時(shí)使用并且做好充分的測(cè)試。5. 一個(gè)可復(fù)用的字體適配工具類設(shè)計(jì)與實(shí)現(xiàn)5.1 工具類的核心接口設(shè)計(jì)經(jīng)過(guò)多個(gè)項(xiàng)目的迭代我總結(jié)出一個(gè)相對(duì)通用的字體適配工具類。它的核心思路是把“是否跟隨系統(tǒng)”“縮放比例”“單位換算”這三個(gè)關(guān)注點(diǎn)分離對(duì)外提供簡(jiǎn)潔的API。public class FontScaleHelper { private static float sAppFontScale 1.0f; private static boolean sFollowSystem true; public static void init(boolean followSystem, float appScale) { sFollowSystem followSystem; sAppFontScale appScale; } public static float getEffectiveScale(Resources res) { float systemScale sFollowSystem ? res.getConfiguration().fontScale : 1.0f; return systemScale * sAppFontScale; } public static float spToPx(float sp, Resources res) { return sp * res.getDisplayMetrics().scaledDensity * getEffectiveScale(res); } }這個(gè)設(shè)計(jì)的巧妙之處在于getEffectiveScale把系統(tǒng)縮放和應(yīng)用自定義縮放合并成一個(gè)系數(shù)后續(xù)所有換算都基于這個(gè)系數(shù)。這樣無(wú)論產(chǎn)品需求怎么變只需要調(diào)整init里的參數(shù)即可。5.2 在BaseActivity中集成有了工具類BaseActivity的集成就很簡(jiǎn)潔了Override protected void attachBaseContext(Context newBase) { Configuration config new Configuration(newBase.getResources().getConfiguration()); if (!FontScaleHelper.isFollowSystem()) { config.fontScale FontScaleHelper.getAppFontScale(); } Context context newBase.createConfigurationContext(config); super.attachBaseContext(context); }注意這里把fontScale設(shè)成了應(yīng)用自定義的縮放值而不是固定1.0。這樣如果產(chǎn)品希望“不跟隨系統(tǒng)但應(yīng)用內(nèi)可以統(tǒng)一調(diào)大”也能支持。5.3 動(dòng)態(tài)刷新已顯示界面的策略如果應(yīng)用運(yùn)行過(guò)程中需要?jiǎng)討B(tài)改變字體大小比如設(shè)置頁(yè)里的“字體大小”選項(xiàng)就需要刷新已顯示的界面。最穩(wěn)妥的方式是重建Activityrecreate();但recreate()會(huì)丟失頁(yè)面狀態(tài)所以要在onSaveInstanceState里保存好必要的數(shù)據(jù)。如果不想重建也可以遍歷View樹(shù)手動(dòng)更新字號(hào)但這種方式容易遺漏而且對(duì)RecyclerView里的Item處理起來(lái)很麻煩。我的建議是除非有強(qiáng)烈的性能要求否則優(yōu)先用recreate()。它雖然簡(jiǎn)單粗暴但勝在可靠。配合ViewModel保存數(shù)據(jù)用戶體驗(yàn)上不會(huì)有明顯問(wèn)題。5.4 測(cè)試與驗(yàn)證要點(diǎn)字體適配的測(cè)試不能只靠肉眼觀察要覆蓋以下幾種場(chǎng)景系統(tǒng)字體調(diào)到最小、默認(rèn)、最大三檔分別檢查布局是否正常應(yīng)用內(nèi)動(dòng)態(tài)調(diào)整字體后檢查所有頁(yè)面是否同步更新多語(yǔ)言切換后檢查文字是否溢出或截?cái)鄼M豎屏切換后檢查字體是否保持一致分屏模式下檢查字體是否跟隨窗口大小變化我通常會(huì)在開(kāi)發(fā)者選項(xiàng)里打開(kāi)“顯示布局邊界”這樣能直觀看到文字是否超出了控件范圍。另外Layout Inspector也是排查字體問(wèn)題的好幫手它可以實(shí)時(shí)查看每個(gè)TextView的實(shí)際字號(hào)和測(cè)量尺寸。6. 那些年我踩過(guò)的字體適配坑與排查思路6.1 坑一WebView里的字體不跟隨應(yīng)用設(shè)置WebView是個(gè)獨(dú)立的世界它的字體大小由CSS和WebSettings控制不受Android Context的fontScale影響。如果產(chǎn)品要求WebView里的文字也不跟隨系統(tǒng)需要在加載URL時(shí)注入CSS或者調(diào)用setTextZoom(100)。但setTextZoom有個(gè)坑它設(shè)置的是百分比100表示不縮放。如果系統(tǒng)字體很大WebView默認(rèn)會(huì)跟隨系統(tǒng)縮放這時(shí)候setTextZoom(100)能把它拉回來(lái)。但如果網(wǎng)頁(yè)本身用了rem或em單位可能還需要額外處理。6.2 坑二Toast和Dialog的字體不受Activity控制Toast和Dialog如果用的是Application的Context那么它們不會(huì)受到BaseActivity里Configuration替換的影響。解決辦法是給它們傳入Activity的Context或者單獨(dú)為它們創(chuàng)建配置好的Context。Dialog的情況更復(fù)雜一些因?yàn)锳lertDialog內(nèi)部會(huì)用自己的Context去inflate布局。如果直接傳Activity的Context通常沒(méi)問(wèn)題但如果用了DialogFragment它默認(rèn)用的是Fragment的Context需要確保Fragment所在的Activity已經(jīng)處理過(guò)字體配置。6.3 坑三自定義View在構(gòu)造函數(shù)里讀取字體尺寸自定義View如果在構(gòu)造函數(shù)里通過(guò)TypedArray讀取了textSize那么這個(gè)值是在View創(chuàng)建時(shí)確定的。如果之后系統(tǒng)字體變了View重建了但構(gòu)造函數(shù)里讀到的還是舊值就會(huì)不一致。解決辦法是在onConfigurationChanged里重新讀取或者干脆不在構(gòu)造函數(shù)里讀而是在onDraw或onMeasure時(shí)動(dòng)態(tài)獲取。后者性能稍差但一致性更好。6.4 坑四RecyclerView的ItemDecoration與字體聯(lián)動(dòng)ItemDecoration里的間距通常是固定值但如果字體變大Item高度變了間距看起來(lái)就不協(xié)調(diào)了。這時(shí)候需要在getItemOffsets里根據(jù)當(dāng)前字體縮放比例動(dòng)態(tài)計(jì)算間距。這個(gè)坑比較隱蔽因?yàn)殚g距不對(duì)不會(huì)導(dǎo)致崩潰只是視覺(jué)上不舒服。我是在一次用戶反饋里才注意到的用戶說(shuō)“調(diào)大字體后列表項(xiàng)之間擠在一起了”。排查后發(fā)現(xiàn)是ItemDecoration的間距沒(méi)跟著變。6.5 排查鏈路與工具遇到字體問(wèn)題時(shí)我通常按這個(gè)順序排查確認(rèn)問(wèn)題范圍是單個(gè)頁(yè)面還是全局是所有文字還是特定控件檢查Context來(lái)源出問(wèn)題的控件用的是哪個(gè)Context是否經(jīng)過(guò)了字體配置處理檢查單位用的是sp還是dpsetTextSize傳的是什么單位檢查布局父布局是否限制了高度是否用了wrap_content檢查第三方庫(kù)是否有庫(kù)內(nèi)部寫(xiě)死了字號(hào)或用了自己的Context工具方面除了前面提到的Layout Inspectoradb shell dumpsys activity top也能看到當(dāng)前Activity的Configuration信息包括fontScale的值。這個(gè)命令在排查“為什么這個(gè)頁(yè)面跟其他頁(yè)面不一樣”時(shí)特別有用。7. 關(guān)于字體適配我的幾點(diǎn)個(gè)人體會(huì)字體適配這件事技術(shù)難度不算高但瑣碎程度很高。它不像性能優(yōu)化那樣有明確的指標(biāo)也不像架構(gòu)設(shè)計(jì)那樣有清晰的邊界它更像是“細(xì)節(jié)的堆砌”——每一個(gè)小問(wèn)題單獨(dú)看都不難但湊在一起就容易讓人煩躁。我的第一條體會(huì)是盡早統(tǒng)一方案。項(xiàng)目初期就確定好字體適配策略是跟隨系統(tǒng)還是不跟隨是全局處理還是局部處理然后寫(xiě)進(jìn)開(kāi)發(fā)規(guī)范里。不要等到頁(yè)面做了一大半才想起來(lái)處理那時(shí)候改造成本會(huì)高很多。第二條體會(huì)是不要迷信“最佳實(shí)踐”。網(wǎng)上很多文章說(shuō)“全用dp”或者“全用sp”但實(shí)際項(xiàng)目里往往是混合使用的。關(guān)鍵是要理解每種方案的適用場(chǎng)景和副作用然后根據(jù)產(chǎn)品需求做取舍。第三條體會(huì)是測(cè)試要覆蓋極端情況。系統(tǒng)字體調(diào)到最大、最小多語(yǔ)言切換分屏模式這些場(chǎng)景在開(kāi)發(fā)階段很容易被忽略但恰恰是問(wèn)題最多的地方。我現(xiàn)在的習(xí)慣是每個(gè)版本提測(cè)前都會(huì)專門(mén)花半小時(shí)把這些場(chǎng)景過(guò)一遍。最后分享一個(gè)小技巧如果你不確定某個(gè)控件的字體是從哪里來(lái)的可以在TextView的setTextSize方法上下斷點(diǎn)然后看調(diào)用棧。這個(gè)方法雖然笨但非常有效尤其是排查第三方庫(kù)的字體問(wèn)題時(shí)能快速定位到是哪一層設(shè)置的。