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或许在多线程环境下性能表现会更好些。