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或許在多線程環境下性能表現會更好些。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM