公司客户端开发分为IOS组、Android组与HTML5组,在字体统一上就出现了问题,需要Android采用IOS的字体,这样就只能替换APP字体了。现在才发现Android开发有时候就是很苦逼,特别是拥有IOS开发组的公司,因为不管在设计风格、逻辑、效果都要以IOS为准,其实大家上网搜索下就会发现许多Android的效果都是模仿IOS的(例如横向滑动删除),这种趋势让身为Android程序员情何以堪啊,让Google情何以堪啊。
回到主题,切换APP字体的思路一般都会想到自定义控件(TextView、EditView),但是线上的项目中得有多少这些控件啊,就算用Stuido替换工具来替换也会承担一些风险和资源的消耗,主要是这种思路太死板了,就考虑Android底层应该在字体设置上有放开的方法,然后可以通过Application对控件进行过滤与替换,通过一番搜索果然有所发现,下面贴出代码:
使用方法:
1、请在Application中添加以下代码替换全局字体 FontUtils.getInstance().replaceSystemDefaultFontFromAsset(this, "fonts/xxx.ttf"); 2、请在设置主题代码中添加以下代码- monospace
package core.util;
import android.app.Application;
import android.content.Context;
import android.graphics.Typeface;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.lang.ref.SoftReference;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class FontUtils {
private static final String TAG = FontUtils.class.getSimpleName();
private Map> mCache = new HashMap<>();
private static FontUtils sSingleton = null;
public static Typeface DEFAULT = Typeface.DEFAULT;
// disable instantiate
private FontUtils() {}
public static FontUtils getInstance() {
// double check
if (sSingleton == null) {
synchronized(FontUtils.class) {
if (sSingleton == null) {
sSingleton = new FontUtils();
}
}
}
return sSingleton;
}
/**
* Replace the font of specified view and it's children
* @param root The root view.
* @param fontPath font file path relative to 'assets' directory.
*/
public void replaceFontFromAsset(@NonNull View root, @NonNull String fontPath) {
replaceFont(root, createTypefaceFromAsset(root.getContext(), fontPath));
}
/**
* Replace the font of specified view and it's children
* @param root The root view.
* @param fontPath font file path relative to 'assets' directory.
* @param style One of {@link Typeface#NORMAL}, {@link Typeface#BOLD}, {@link Typeface#ITALIC}, {@link Typeface#BOLD_ITALIC}
*/
public void replaceFontFromAsset(@NonNull View root, @NonNull String fontPath, int style) {
replaceFont(root, createTypefaceFromAsset(root.getContext(), fontPath), style);
}
/**
* Replace the font of specified view and it's children
* @param root The root view.
* @param fontPath The full path to the font data.
*/
public void replaceFontFromFile(@NonNull View root, @NonNull String fontPath) {
replaceFont(root, createTypefaceFromFile(fontPath));
}
/**
* Replace the font of specified view and it's children
* @param root The root view.
* @param fontPath The full path to the font data.
* @param style One of {@link Typeface#NORMAL}, {@link Typeface#BOLD}, {@link Typeface#ITALIC}, {@link Typeface#BOLD_ITALIC}
*/
public void replaceFontFromFile(@NonNull View root, @NonNull String fontPath, int style) {
replaceFont(root, createTypefaceFromFile(fontPath), style);
}
/**
* Replace the font of specified view and it's children with specified typeface
*/
private void replaceFont(@NonNull View root, @NonNull Typeface typeface) {
if (root == null || typeface == null) {
return;
}
if (root instanceof TextView) { // If view is TextView or it's subclass, replace it's font
TextView textView = (TextView)root;
// Extract previous style of TextView
int style = Typeface.NORMAL;
if (textView.getTypeface() != null) {
style = textView.getTypeface().getStyle();
}
textView.setTypeface(typeface, style);
} else if (root instanceof ViewGroup) { // If view is ViewGroup, apply this method on it's child views
ViewGroup viewGroup = (ViewGroup) root;
for (int i = 0; i < viewGroup.getChildCount(); ++i) {
replaceFont(viewGroup.getChildAt(i), typeface);
}
} // else return
}
/**
* Replace the font of specified view and it's children with specified typeface and text style
* @param style One of {@link Typeface#NORMAL}, {@link Typeface#BOLD}, {@link Typeface#ITALIC}, {@link Typeface#BOLD_ITALIC}
*/
private void replaceFont(@NonNull View root, @NonNull Typeface typeface, int style) {
if (root == null || typeface == null) {
return;
}
if (style < 0 || style > 3) {
style = Typeface.NORMAL;
}
if (root instanceof TextView) { // If view is TextView or it's subclass, replace it's font
TextView textView = (TextView)root;
textView.setTypeface(typeface, style);
} else if (root instanceof ViewGroup) { // If view is ViewGroup, apply this method on it's child views
ViewGroup viewGroup = (ViewGroup) root;
for (int i = 0; i < viewGroup.getChildCount(); ++i) {
replaceFont(viewGroup.getChildAt(i), typeface, style);
}
} // else return
}
/**
* Create a Typeface instance with specified font file
* @param fontPath font file path relative to 'assets' directory.
* @return Return created typeface instance.
*/
private Typeface createTypefaceFromAsset(Context context, String fontPath) {
SoftReference typefaceRef = mCache.get(fontPath);
Typeface typeface = null;
if (typefaceRef == null || (typeface = typefaceRef.get()) == null) {
typeface = Typeface.createFromAsset(context.getAssets(), fontPath);
typefaceRef = new SoftReference<>(typeface);
mCache.put(fontPath, typefaceRef);
}
return typeface;
}
private Typeface createTypefaceFromFile(String fontPath) {
SoftReference typefaceRef = mCache.get(fontPath);
Typeface typeface = null;
if (typefaceRef == null || (typeface = typefaceRef.get()) == null) {
typeface = Typeface.createFromFile(fontPath);
typefaceRef = new SoftReference<>(typeface);
mCache.put(fontPath, typefaceRef);
}
return typeface;
}
/**
* Replace system default font. Note:you should also add code below to your app theme in styles.xml.
* {@code - monospace
}
* The best place to call this method is {@link Application#onCreate()}, it will affect
* whole app font.If you call this method after view is visible, you need to invalid the view to make it effective.
* @param context {@link Context Context}
* @param fontPath font file path relative to 'assets' directory.
*/
public void replaceSystemDefaultFontFromAsset(@NonNull Context context, @NonNull String fontPath) {
replaceSystemDefaultFont(createTypefaceFromAsset(context, fontPath));
}
/**
* Replace system default font. Note:you should also add code below to your app theme in styles.xml.
* {@code - monospace
}
* The best place to call this method is {@link Application#onCreate()}, it will affect
* whole app font.If you call this method after view is visible, you need to invalid the view to make it effective.
* @param context {@link Context Context}
* @param fontPath The full path to the font data.
*/
public void replaceSystemDefaultFontFromFile(@NonNull Context context, @NonNull String fontPath) {
replaceSystemDefaultFont(createTypefaceFromFile(fontPath));
}
/**
* Replace system default font. Note:you should also add code below to your app theme in styles.xml.
* {@code - monospace
}
* The best place to call this method is {@link Application#onCreate()}, it will affect
* whole app font.If you call this method after view is visible, you need to invalid the view to make it effective.
*/
private void replaceSystemDefaultFont(@NonNull Typeface typeface) {
modifyObjectField(null, "MONOSPACE", typeface);
}
private void modifyObjectField(Object obj, String fieldName, Object value) {
try {
Field defaultField = Typeface.class.getDeclaredField(fieldName);
defaultField.setAccessible(true);
defaultField.set(obj, value);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
这个工具类几乎完美,的确有许多值得学习的地方,比如在单例设置是采用了synchronized 摒弃了懒汉的模式,在资源使用上用到了SoftReference 软引用,在缓存上用了HashMap,最后采用反射赋值,这几点都是可圈可点。如果将缓存的HashMap换成ConcurrentHashMap或许在多线程环境下性能表现会更好些。
下面是该工具类作者的GitHub:https://github.com/whinc