原文地址java
首先將項目須要的字體資源放置在app下:android
注意,字體ttf文件只能用英文字母,中文會報找不到文件異常。app
未設置以前的佈局樣式: ide
字體文件準備好後,咱們就能夠按需設置本身想要的字體樣式。下面提供了3種設置方法,這3種方法均可以改變能夠顯示文本的控件字體樣式,如TextView,Button,EditText,CheckBox,RadioButton等等:工具
重寫TextView,重寫其中的setTypeface方法,設置本身的字體樣式佈局
package com.guorentong.learn.myapplication; import android.content.Context; import android.graphics.Typeface; import android.util.AttributeSet; import android.widget.TextView; public class FontTextView extends android.support.v7.widget.AppCompatTextView { public FontTextView(Context context) { super(context); } public FontTextView(Context context, AttributeSet attrs) { super(context, attrs); } public FontTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } private Typeface createTypeface(Context context, String fontPath) { return Typeface.createFromAsset(context.getAssets(), fontPath); } @Override public void setTypeface(Typeface tf, int style) { super.setTypeface(createTypeface(getContext(),"fonts/pop.ttf"), style); } }
佈局中直接用自定義的FontTextView類便可。性能
<com.guorentong.learn.myapplication.FontTextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="這是一個TextView"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="這是一個Button"/> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="這是一個EditText"/> <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="這是一個CheckBox"/> <RadioButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="這是一個RadioButton"/>
這種設置方式的優缺點:
優勢:使用簡單方便,不須要額外的工做。
缺點:只能替換一類控件的字體,若是須要替換Button或EditText控件的字體,須要以相同的方式自定義這些控件,這樣工做量大。字體
Android中可顯示文本的控件都直接或間接繼承自TextView,批量替換字體的原理就是從指定的View節點開始遞歸遍歷全部子View,若是子View類型是TextView類型或其子類型則替換字體,若是子View是ViewGroup類型則重複這一過程。我抽取了一個工具類,代碼以下:this
package com.guorentong.learn.myapplication; import android.app.Activity; import android.content.Context; import android.graphics.Typeface; import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; public class TypefaceUtil { /** * <p>Replace the font of specified view and it's children</p> * @param root The root view. * @param fontPath font file path relative to 'assets' directory. */ public static void replaceFont(@NonNull View root, String fontPath) { if (root == null || TextUtils.isEmpty(fontPath)) { return; } if (root instanceof TextView) { // If view is TextView or it's subclass, replace it's font TextView textView = (TextView)root; int style = Typeface.NORMAL; if (textView.getTypeface() != null) { style = textView.getTypeface().getStyle(); } textView.setTypeface(createTypeface(root.getContext(), fontPath), style); } else if (root instanceof ViewGroup) { // If view is ViewGroup, apply this method on it's child views ViewGroup viewGroup = (ViewGroup) root; for (int i = 0; i < viewGroup.getChildCount(); ++i) { replaceFont(viewGroup.getChildAt(i), fontPath); } } } /** * <p>Replace the font of specified view and it's children</p> * @param context The view corresponding to the activity. * @param fontPath font file path relative to 'assets' directory. */ public static void replaceFont(@NonNull Activity context, String fontPath) { replaceFont(getRootView(context),fontPath); Log.e("TAG", "fontPath: --------------------"+fontPath ); } /* * Create a Typeface instance with your font file */ public static Typeface createTypeface(Context context, String fontPath) { return Typeface.createFromAsset(context.getAssets(), fontPath); } /** * 從Activity 獲取 rootView 根節點 * @param context * @return 當前activity佈局的根節點 */ public static View getRootView(Activity context) { return ((ViewGroup)context.findViewById(android.R.id.content)).getChildAt(0); } }
代碼中調用,這裏建議最好放在BaseActivity中,由於this只能表明當前頁面,放在BaseActivity中讓全部Activity繼承自BaseActivity能夠全局的改變字體樣式:spa
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getLayoutId()); //設置字體改變 TypefaceUtil.replaceFont(this, "fonts/pop.ttf"); }
這種設置方式的優缺點:
優勢:不須要修改XML佈局文件,不須要重寫控件,能夠批量替換全部繼承自TextView的控件的字體,適合須要批量替換字體的場合,如程序的默認字體。
缺點:若是要替換整個App的全部字體,須要在每一個有界面的地方批量替換一次,頁面多了仍是有些工做量的,不過能夠在Activity和Fragment的基類中完成這個工做。其次,性能可能差一點,畢竟要遞歸遍歷全部子節點(不過實際使用中沒有明顯的性能降低程序依然流暢)。
App中顯示的字體來自於Typeface中的預約義的字體,這種方式是經過改變系統字體樣式改變字體。
首先須要改變APP的BaseTheme
<!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <!-- Set system default typeface --> <item name="android:typeface">monospace</item> </style>
再而後我將須要的方法又抽取了一下,和以前的TypefaceUtil造成了一個完整的工具類,代碼以下:
package com.guorentong.learn.myapplication; import android.app.Activity; import android.content.Context; import android.graphics.Typeface; import android.support.annotation.NonNull; import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.lang.reflect.Field; public class TypefaceUtils { /** * 爲給定的字符串添加HTML紅色標記,當使用Html.fromHtml()方式顯示到TextView 的時候其將是紅色的 * * @param string 給定的字符串 * @return */ public static String addHtmlRedFlag(String string) { return "<font color=\"red\">" + string + "</font>"; } /** * 將給定的字符串中全部給定的關鍵字標紅 * * @param sourceString 給定的字符串 * @param keyword 給定的關鍵字 * @return 返回的是帶Html標籤的字符串,在使用時要經過Html.fromHtml()轉換爲Spanned對象再傳遞給TextView對象 */ public static String keywordMadeRed(String sourceString, String keyword) { String result = ""; if (sourceString != null && !"".equals(sourceString.trim())) { if (keyword != null && !"".equals(keyword.trim())) { result = sourceString.replaceAll(keyword, "<font color=\"red\">" + keyword + "</font>"); } else { result = sourceString; } } return result; } /** * <p>Replace the font of specified view and it's children</p> * @param root The root view. * @param fontPath font file path relative to 'assets' directory. */ public static void replaceFont(@NonNull View root, String fontPath) { if (root == null || TextUtils.isEmpty(fontPath)) { return; } if (root instanceof TextView) { // If view is TextView or it's subclass, replace it's font TextView textView = (TextView)root; int style = Typeface.NORMAL; if (textView.getTypeface() != null) { style = textView.getTypeface().getStyle(); } textView.setTypeface(createTypeface(root.getContext(), fontPath), style); } else if (root instanceof ViewGroup) { // If view is ViewGroup, apply this method on it's child views ViewGroup viewGroup = (ViewGroup) root; for (int i = 0; i < viewGroup.getChildCount(); ++i) { replaceFont(viewGroup.getChildAt(i), fontPath); } } } /** * <p>Replace the font of specified view and it's children</p> * 經過遞歸批量替換某個View及其子View的字體改變Activity內部控件的字體(TextView,Button,EditText,CheckBox,RadioButton等) * @param context The view corresponding to the activity. * @param fontPath font file path relative to 'assets' directory. */ public static void replaceFont(@NonNull Activity context, String fontPath) { replaceFont(getRootView(context),fontPath); } /* * Create a Typeface instance with your font file */ public static Typeface createTypeface(Context context, String fontPath) { return Typeface.createFromAsset(context.getAssets(), fontPath); } /** * 從Activity 獲取 rootView 根節點 * @param context * @return 當前activity佈局的根節點 */ public static View getRootView(Activity context) { return ((ViewGroup)context.findViewById(android.R.id.content)).getChildAt(0); } /** * 經過改變App的系統字體替換App內部全部控件的字體(TextView,Button,EditText,CheckBox,RadioButton等) * @param context * @param fontPath * 須要修改style樣式爲monospace: */ // <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> // <!-- Customize your theme here. --> // <!-- Set system default typeface --> // <item name="android:typeface">monospace</item> // </style> public static void replaceSystemDefaultFont(@NonNull Context context, @NonNull String fontPath) { replaceTypefaceField("MONOSPACE", createTypeface(context, fontPath)); } /** * <p>Replace field in class Typeface with reflection.</p> */ private static void replaceTypefaceField(String fieldName, Object value) { try { Field defaultField = Typeface.class.getDeclaredField(fieldName); defaultField.setAccessible(true); defaultField.set(null, value); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
代碼中引用只須要在BaseApplication的onCreate裏設置:
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); //設置字體改變 TypefaceUtils.replaceSystemDefaultFont(this,"fonts/pop.ttf"); setContentView(getLayoutId()); }
這種設置方式的優缺點:
優勢:方式2的優勢+更加簡潔
缺點:字體文件通常比較大,加載時間長並且佔內存(不過實際使用中沒有明顯的性能降低程序依然流暢)。
我通常都是用第2,3種,簡潔高效,如今說一下如何在我的設置裏邊改變你的app字體:
經實踐,第2種方法是最好的,能夠實時更新頁面。而第三種須要返回從新進入到activity纔會看到效果。
先在BaseActivity註冊一個字體改變監聽的廣播
package com.guorentong.learn.myapplication; import android.app.Activity; import android.app.Application; import android.content.BroadcastReceiver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.view.View; public abstract class BaseActivity extends AppCompatActivity { private TypefaceChangeReceiver typefaceChangeReceiver; public static Context context; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // TypefaceUtils.replaceSystemDefaultFont(this,"fonts/pop.ttf"); setContentView(getLayoutId()); // TypefaceUtil.replaceFont(this, "fonts/pop.ttf"); //改變新建立Activity的字體 onTypefaceChange(PreferencesUtil.getString(ConstantValue.AppSetting.SHPNAME,ConstantValue.AppSetting.typeface)); typefaceChangeReceiver = new TypefaceChangeReceiver(); IntentFilter typefaceFilter = new IntentFilter(); typefaceFilter.addAction(ConstantValue.ReceiverAction.TYPEFACE_ACTION); registerReceiver(typefaceChangeReceiver,typefaceFilter); initData(); } /** * 設置當前activity的layout * @return 當前界面的佈局id */ protected abstract int getLayoutId(); protected abstract void initData(); @Override protected void onDestroy() { unregisterReceiver(typefaceChangeReceiver); super.onDestroy(); } /** * 字體改變 */ protected void onTypefaceChange(String typeface){ //第2種 // TypefaceUtil.replaceFont(this, typeface); //第3種 TypefaceUtils.replaceSystemDefaultFont(this,typeface); } /** * 字體改變監聽,用於改變整個APP字體 */ public class TypefaceChangeReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if(ConstantValue.ReceiverAction.TYPEFACE_ACTION.equals(intent.getAction())){ String typeface = intent.getStringExtra("typeface"); //改變未銷燬尚存在的Activity的字體 onTypefaceChange(typeface); } } } }
常量抽取出來,放到ConstantValue常量類中:
package com.guorentong.learn.myapplication; public class ConstantValue { interface AppSetting{ String SHPNAME = "SETTING"; String typeface = "typeface"; } interface ReceiverAction{ String TYPEFACE_ACTION = "font.TYPEFACE_CHANGE"; } interface Typeface{ String POP = "fonts/pop.ttf";//pop String FZZY = "fonts/fzzy.TTF";//方正準圓 String HWCY = "fonts/hwcy.TTF";//華文彩雲 String HWXK = "fonts/hwxk.ttf";//華文行楷 String HWXS = "fonts/hwxs.ttf";//華文新宋 String HWXW = "fonts/hwxw.TTF";//華文新魏 String YY = "fonts/yy.ttf";//幼圓 } }
在SettingActivity裏發送特定的廣播便可:
@Override public void onClick(View v) { Intent intent = new Intent(ConstantValue.ReceiverAction.TYPEFACE_ACTION); String typeface = null; switch (v.getId()) { default: break; case R.id.but0: typeface = ConstantValue.Typeface.POP; break; case R.id.but1: typeface = ConstantValue.Typeface.FZZY; break; case R.id.but2: typeface = ConstantValue.Typeface.HWCY; break; case R.id.but3: typeface = ConstantValue.Typeface.HWXK; break; case R.id.but4: typeface = ConstantValue.Typeface.HWXS; break; case R.id.but5: typeface = ConstantValue.Typeface.HWXW; break; case R.id.but6: typeface = ConstantValue.Typeface.YY; break; } //保存字體設置 PreferencesUtil.put(ConstantValue.AppSetting.SHPNAME, ConstantValue.AppSetting.typeface, typeface); intent.putExtra("typeface", typeface); sendBroadcast(intent); }
這裏有兩個動做,
一個是發送廣播,用於修改以前建立了但並未銷燬的Activity的字體;
另外一個是保存設置的字體,用於修改以後將建立的Activity的字體。
PreferencesUtil的一部分代碼:
package com.guorentong.learn.myapplication; import android.content.Context; import android.content.SharedPreferences; import android.support.annotation.NonNull; import java.util.Set; public class PreferencesUtil { private static Context context = BaseApplication.getContext(); private final static String DEFAULT_STRING_VALUE = ""; public static void put(@NonNull String SHPNAME, @NonNull String key, Object value){ SharedPreferences shp = context.getSharedPreferences(SHPNAME, Context.MODE_PRIVATE); SharedPreferences.Editor edit = shp.edit(); if(value instanceof String) edit.putString(key,(String)value); if(value instanceof Boolean) edit.putBoolean(key, (Boolean) value); if(value instanceof Float) edit.putFloat(key, (Float) value); if(value instanceof Long) edit.putLong(key, (Long) value); if(value instanceof Integer) edit.putInt(key, (Integer) value); if(value instanceof Set) edit.putStringSet(key, (Set<String>) value); edit.apply(); } public static String getString(@NonNull String SHPNAME,@NonNull String key){ SharedPreferences shp = context.getSharedPreferences(SHPNAME, Context.MODE_PRIVATE); return shp.getString(key, DEFAULT_STRING_VALUE); } }
這樣便實現了在一個SettingActivity裏改變全局字體的功能。
使用注意:
1.若是字體文件比較大,當設置後可能並不會當即生效,有1~2s的延遲,具體還依據類中控件的數量來定。
2.相當重要,全部的Activity請務必要繼承BaseActivity。
一、在清單文件裏註冊
android:allowBackup="true" android:name="BaseApplication"
public class BaseApplication extends Application { private static BaseApplication mApplication; @Override public void onCreate() { super.onCreate(); mApplication = this; } public static Context getContext(){ return mApplication; } }