Android替換APP字體 — Typeface
APP字體的思路一般都會想到自定義控件(TextView、EditView),但是項目中會有很多種控件,就算用也會承擔一些風險和資源的消耗,主要是這種思路太死板了,就考慮Android底層應該在字體設置上有放開的方法,然后可以通過Application對控件進行過濾與替換,通過一番搜索果然有所發現,下面貼出代碼:
1、請在Application中添加以下代碼替換全局字體
// 字體放在 assets 文件夾下 FontUtils.getInstance().replaceSystemDefaultFontFromAsset(this, "fonts/xxx.ttf"); // .otf 字體文件也可
2、請在設置主題代碼中添加以下代碼
主題代碼為 application 中的theme屬性的 style 里面。
<item name="android:typeface">monospace</item>
3、新建文件FontUtils.java
1 package com.test.bean; 2 import java.lang.ref.SoftReference; 3 import java.lang.reflect.Field; 4 import java.util.HashMap; 5 import java.util.Map; 6 7 import android.app.Application; 8 import android.content.Context; 9 import android.graphics.Typeface; 10 import android.view.View; 11 import android.view.ViewGroup; 12 import android.widget.TextView; 13 14 public class FontUtils { 15 16 private Map<String, SoftReference<Typeface>> mCache = new HashMap<String, SoftReference<Typeface>>(); 17 private static FontUtils sSingleton = null; 18 19 public static Typeface DEFAULT = Typeface.DEFAULT; 20 21 // disable instantiate 22 private FontUtils() {} 23 24 public static FontUtils getInstance() { 25 // double check 26 if (sSingleton == null) { 27 synchronized(FontUtils.class) { 28 if (sSingleton == null) { 29 sSingleton = new FontUtils(); 30 } 31 } 32 } 33 return sSingleton; 34 } 35 36 /** 37 * <p>Replace the font of specified view and it's children</p> 38 * @param root The root view. 39 * @param fontPath font file path relative to 'assets' directory. 40 */ 41 public void replaceFontFromAsset(View root, String fontPath) { 42 replaceFont(root, createTypefaceFromAsset(root.getContext(), fontPath)); 43 } 44 45 /** 46 * <p>Replace the font of specified view and it's children</p> 47 * @param root The root view. 48 * @param fontPath font file path relative to 'assets' directory. 49 * @param style One of {@link Typeface#NORMAL}, {@link Typeface#BOLD}, {@link Typeface#ITALIC}, {@link Typeface#BOLD_ITALIC} 50 */ 51 public void replaceFontFromAsset(View root, String fontPath, int style) { 52 replaceFont(root, createTypefaceFromAsset(root.getContext(), fontPath), style); 53 } 54 55 /** 56 * <p>Replace the font of specified view and it's children</p> 57 * @param root The root view. 58 * @param fontPath The full path to the font data. 59 */ 60 public void replaceFontFromFile(View root, String fontPath) { 61 replaceFont(root, createTypefaceFromFile(fontPath)); 62 } 63 64 /** 65 * <p>Replace the font of specified view and it's children</p> 66 * @param root The root view. 67 * @param fontPath The full path to the font data. 68 * @param style One of {@link Typeface#NORMAL}, {@link Typeface#BOLD}, {@link Typeface#ITALIC}, {@link Typeface#BOLD_ITALIC} 69 */ 70 public void replaceFontFromFile(View root, String fontPath, int style) { 71 replaceFont(root, createTypefaceFromFile(fontPath), style); 72 } 73 74 /** 75 * <p>Replace the font of specified view and it's children with specified typeface</p> 76 */ 77 private void replaceFont(View root, Typeface typeface) { 78 if (root == null || typeface == null) { 79 return; 80 } 81 82 if (root instanceof TextView) { // If view is TextView or it's subclass, replace it's font 83 TextView textView = (TextView)root; 84 // Extract previous style of TextView 85 int style = Typeface.NORMAL; 86 if (textView.getTypeface() != null) { 87 style = textView.getTypeface().getStyle(); 88 } 89 textView.setTypeface(typeface, style); 90 } else if (root instanceof ViewGroup) { // If view is ViewGroup, apply this method on it's child views 91 ViewGroup viewGroup = (ViewGroup) root; 92 for (int i = 0; i < viewGroup.getChildCount(); ++i) { 93 replaceFont(viewGroup.getChildAt(i), typeface); 94 } 95 } // else return 96 } 97 98 /** 99 * <p>Replace the font of specified view and it's children with specified typeface and text style</p> 100 * @param style One of {@link Typeface#NORMAL}, {@link Typeface#BOLD}, {@link Typeface#ITALIC}, {@link Typeface#BOLD_ITALIC} 101 */ 102 private void replaceFont(View root, Typeface typeface, int style) { 103 if (root == null || typeface == null) { 104 return; 105 } 106 if (style < 0 || style > 3) { 107 style = Typeface.NORMAL; 108 } 109 110 if (root instanceof TextView) { // If view is TextView or it's subclass, replace it's font 111 TextView textView = (TextView)root; 112 textView.setTypeface(typeface, style); 113 } else if (root instanceof ViewGroup) { // If view is ViewGroup, apply this method on it's child views 114 ViewGroup viewGroup = (ViewGroup) root; 115 for (int i = 0; i < viewGroup.getChildCount(); ++i) { 116 replaceFont(viewGroup.getChildAt(i), typeface, style); 117 } 118 } // else return 119 } 120 121 /** 122 * <p>Create a Typeface instance with specified font file</p> 123 * @param fontPath font file path relative to 'assets' directory. 124 * @return Return created typeface instance. 125 */ 126 private Typeface createTypefaceFromAsset(Context context, String fontPath) { 127 SoftReference<Typeface> typefaceRef = mCache.get(fontPath); 128 Typeface typeface = null; 129 if (typefaceRef == null || (typeface = typefaceRef.get()) == null) { 130 typeface = Typeface.createFromAsset(context.getAssets(), fontPath); 131 typefaceRef = new SoftReference<Typeface>(typeface); 132 mCache.put(fontPath, typefaceRef); 133 } 134 return typeface; 135 } 136 137 private Typeface createTypefaceFromFile(String fontPath) { 138 SoftReference<Typeface> typefaceRef = mCache.get(fontPath); 139 Typeface typeface = null; 140 if (typefaceRef == null || (typeface = typefaceRef.get()) == null) { 141 typeface = Typeface.createFromFile(fontPath); 142 typefaceRef = new SoftReference<Typeface>(typeface); 143 mCache.put(fontPath, typefaceRef); 144 } 145 return typeface; 146 } 147 148 /** 149 * <p>Replace system default font. <b>Note:</b>you should also add code below to your app theme in styles.xml. </p> 150 * {@code <item name="android:typeface">monospace</item>} 151 * <p>The best place to call this method is {@link Application#onCreate()}, it will affect 152 * whole app font.If you call this method after view is visible, you need to invalid the view to make it effective.</p> 153 * @param context {@link Context Context} 154 * @param fontPath font file path relative to 'assets' directory. 155 */ 156 public void replaceSystemDefaultFontFromAsset(Context context, String fontPath) { 157 replaceSystemDefaultFont(createTypefaceFromAsset(context, fontPath)); 158 } 159 160 /** 161 * <p>Replace system default font. <b>Note:</b>you should also add code below to your app theme in styles.xml. </p> 162 * {@code <item name="android:typeface">monospace</item>} 163 * <p>The best place to call this method is {@link Application#onCreate()}, it will affect 164 * whole app font.If you call this method after view is visible, you need to invalid the view to make it effective.</p> 165 * @param context {@link Context Context} 166 * @param fontPath The full path to the font data. 167 */ 168 public void replaceSystemDefaultFontFromFile(Context context, String fontPath) { 169 replaceSystemDefaultFont(createTypefaceFromFile(fontPath)); 170 } 171 172 /** 173 * <p>Replace system default font. <b>Note:</b>you should also add code below to your app theme in styles.xml. </p> 174 * {@code <item name="android:typeface">monospace</item>} 175 * <p>The best place to call this method is {@link Application#onCreate()}, it will affect 176 * whole app font.If you call this method after view is visible, you need to invalid the view to make it effective.</p> 177 */ 178 private void replaceSystemDefaultFont(Typeface typeface) { 179 modifyObjectField(null, "MONOSPACE", typeface); 180 } 181 182 private void modifyObjectField(Object obj, String fieldName, Object value) { 183 try { 184 Field defaultField = Typeface.class.getDeclaredField(fieldName); 185 defaultField.setAccessible(true); 186 defaultField.set(obj, value); 187 188 } catch (NoSuchFieldException e) { 189 e.printStackTrace(); 190 } catch (IllegalAccessException e) { 191 e.printStackTrace(); 192 } 193 } 194 }
核心代碼在:replaceFont方法,替換TextView的字體,那大家就會疑問了,這個工具類只替換了Textview的字體,那如果用了EditView、RadioButton等呢。大家可以看下那些控件的父類,它們都是繼承TextView,這樣就豁然開朗,細節果然決定成敗啊。整個工具類在字體替換的效率上都有所體現,采用軟引用和HashMap緩存策略大大降低替換時的資源消耗,考慮的確很全面,並采用反射機制對Typeface進行設置達到換字體的目的。
這個工具類的確有許多值得學習的地方,比如在單例設置是采用了synchronized 摒棄了懶漢的模式,在資源使用上用到了SoftReference 軟引用,在緩存上用了HashMap,最后采用反射賦值,這幾點都是可圈可點。如果將緩存的HashMap換成ConcurrentHashMap或許在多線程環境下性能表現會更好些。
