Android替换APP字体 — Typeface

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

原文地址:https://www.cnblogs.com/steffen/p/5854063.html