前言
近期項目需要在我們的APP中使用指定的字體庫。經過搜集資料,研讀源碼,和別人探討請教,最終產出了一些比較好的方案。不敢專享,寫成文章分享出來,希望對大家的實際開發工作有所幫助。喜歡探討Android開發技術的同學可以加學習小組QQ群: 193765960。
本文只總結了較優方案,其他諸如自定義textView類,遍歷layout_root_view這樣的方案,作者認為限制較大,使用麻煩,就不在這里介紹了,感興趣的朋友請自行百度。
版權歸作者所有,如有轉發,請注明文章出處:https://xiaodanchen.github.io/archives/
Android字體機制介紹
關鍵類:
-
Typeface:
字體類,定義了字體類型到字體庫的映射關系,Android有DEFAULT, MONOSPACE, SERIF, SANS_SERIF幾種字體,根據各自的NORMAL(常規),BOLD(加粗),ITALIC(傾斜),BOLD_ITALIC(加粗傾斜)等幾種樣式,總共可以映射到至少16種字體庫。 -
TextAppearance:
字體外觀類,定義了字體的外觀比如,typeface,textsize,textcolor等外觀屬性。
TextView的字體顯示機制
先看一下TextView的構造方法:
public TextView(Context context);
public TextView(Context context, @Nullable AttributeSet attrs);
public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr);
public TextView(
Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes);
- AttributeSet:xml中設置的屬性
- defStyleAttr:系統默認的屬性
- defStyleRes:系統默認的樣式,這個是我們需要注意的參數哈
Textview的字體設置邏輯:
1)查看xml中是否設置了TextAppearance屬性,如果設置了就判斷外觀中是否設置了字體。否則就執行第二步。
2)查看xml中是否設置了Typeface屬性,指明了字體。否則執行第三步
3)使用系統的默認樣式:defStyleRes
所以,假如我們的xml中對字體沒有做設置,要是想要修改字體又不想修改xml,那么我們就要想其他辦法了。
我最終的方案(方案一)是在APP的theme中去設置修改系統的默認樣式(最終走到這個思路上是經過了比較酸爽的經過的,就不在這里細說了)。
方案一(底層方案):通過反射機制,修改Typeface類的字體庫引用
第一步:通過反射機制修改Typeface字體指向的字體庫到我們的字體庫。
- 定義修改字體庫的方法類(示例):
import java.lang.reflect.Field;
import android.content.Context;
import android.graphics.Typeface;
public final class FontsUtils {
public static void setDefaultFont(Context context,
String staticTypefaceFieldName, String fontAssetName) {
final Typeface regular = Typeface.createFromAsset(context.getAssets(),
fontAssetName);
replaceFont(staticTypefaceFieldName, regular);
}
protected static void replaceFont(String staticTypefaceFieldName,
final Typeface newTypeface) {
try {
final Field staticField = Typeface.class
.getDeclaredField(staticTypefaceFieldName);
staticField.setAccessible(true);
staticField.set(null, newTypeface);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
在工程assets目錄下新建fonts文件夾,把我們需要的字庫放在里面,比如:FZLTHJW.TTF
在MyApplication.oncreate()中調用修改字體庫:
FontsUtils.setDefaultFont(this, "DEFAULT", "fonts/FZLTHJW.TTF");
FontsUtils.setDefaultFont(this, "MONOSPACE", "fonts/FZLTHJW.TTF");
FontsUtils.setDefaultFont(this, "SERIF", "fonts/FZLTHJW.TTF");
FontsUtils.setDefaultFont(this, "SANS_SERIF", "fonts/FZLTHJW.TTF");
第二步:修改APP theme的默認屬性。
<style name="AppTheme" parent="AppBaseTheme">
<item name="android:textViewStyle">@style/FontTextviewstyle</item>
<item name="android:buttonStyle">@style/FontButtonstyle</item>
<item name="editTextStyle">@style/FontEditTextstyle</item>
<item name="android:radioButtonStyle">@style/FontradioButtonstyle</item>
</style>
<style name="FontTextviewstyle" parent="android:style/Widget.TextView">
<item name="android:textAppearance">@style/FontTextAppearance</item>
</style>
<style name="FontButtonstyle" parent="android:style/Widget.Button">
<item name="android:textAppearance">@style/FontTextAppearance</item>
</style>
<style name="FontradioButtonstyle" parent="android:style/Widget.CompoundButton.RadioButton">
<item name="android:textAppearance">@style/FontTextAppearance</item>
</style>
<style name="FontEditTextstyle" parent="Widget.AppCompat.EditText">
<item name="android:textAppearance">@style/FontTextAppearance</item>
</style>
<style name="FontTextAppearance" parent="@android:style/TextAppearance">
<item name="android:typeface">monospace</item>
</style>
總結:
-
優點:
- 不用修改xml,沒有為每個activity創建字體的實例。
- 除了常見的控件外,對Material Design的新控件也有作業
-
缺陷:
- 對于alertDialog還沒有實現style的默認適配
- 因為是修改的底層邏輯,相較于方案二,稍復雜。
方案二(頂層方案):自定義布局加載器,在加載layout_xml時對view tree的 view做字體的邏輯處理
- 使用:如下方代碼所示,在oncreatview的回調中,對view做類型判斷,設置view的字體。
- 優點:該方案代碼邏輯清晰,使用簡單,幾行代碼就可以搞定問題,不用修改xml等。
-
缺陷:
- 在一些第三方的控件或者自定義控件上可能使用會有限制,如果控件沒有提供修改控件字體的接口的話(待驗證)
- 需要注意的是,對于Material Design的android.support.design.widget.TextInputLayout,android.support.design.widget.TabLayout這樣的控件不起作用,需要對這種類型設置*textAppearance這樣的屬性。
private void replaceFont() {
final Typeface typeface = Typeface.createFromAsset(getAssets(), "fonts/fangzheng.ttf");
LayoutInflaterCompat.setFactory(LayoutInflater.from(this), new LayoutInflaterFactory() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
// TODO Auto-generated method stub
AppCompatDelegate delegate = getDelegate();
View view = delegate.createView(parent, name, context, attrs);
if(view != null ){
if(view instanceof TextView){
((TextView)view).setTypeface(typeface);
}else if(view instanceof Button){
((Button)view).setTypeface(typeface);
}else if(view instanceof RadioButton){
((RadioButton)view).setTypeface(typeface);
}
}
return view;
}
});
}
/**
* BaseActivity.java
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
replaceFont();//注意需要在super方法之前調用,否則會報異常
super.onCreate(savedInstanceState);
}
總結:
- 通過這個方案,其實我們應該學習到一種統一對xml viewTree中某種控件設置某種屬性的方法。
- 舉一反三,針對剛才上述的缺陷,我們其實也可以嘗試設置textAppearance屬性(相較于設置typeface麻煩些),感興趣的同學可以去試驗下。