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或许在多线程环境下性能表现会更好些。
本文介绍了一种在Android应用中全局替换字体的方法,通过在Application中调用特定方法并结合主题配置,实现对TextView及其子类字体的一致性替换。利用自定义工具类FontUtils简化替换流程,提高效率。
1231

被折叠的 条评论
为什么被折叠?



