Android之替换APP字体——Typeface

来源:互联网 发布:电脑软件技术培训 编辑:程序博客网 时间:2024/05/16 10:53

        公司客户端开发分为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、请在设置主题代码中添加以下代码<item name="android:typeface">monospace</item>
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<String, SoftReference<Typeface>> 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;    }    /**     * <p>Replace the font of specified view and it's children</p>     * @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));    }    /**     * <p>Replace the font of specified view and it's children</p>     * @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);    }    /**     * <p>Replace the font of specified view and it's children</p>     * @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));    }    /**     * <p>Replace the font of specified view and it's children</p>     * @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);    }    /**     * <p>Replace the font of specified view and it's children with specified typeface</p>     */    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    }    /**     * <p>Replace the font of specified view and it's children with specified typeface and text style</p>     * @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    }    /**     * <p>Create a Typeface instance with specified font file</p>     * @param fontPath font file path relative to 'assets' directory.     * @return Return created typeface instance.     */    private Typeface createTypefaceFromAsset(Context context, String fontPath) {        SoftReference<Typeface> 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<Typeface> 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;    }    /**     * <p>Replace system default font. <b>Note:</b>you should also add code below to your app theme in styles.xml. </p>     * {@code <item name="android:typeface">monospace</item>}     * <p>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.</p>     * @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));    }    /**     * <p>Replace system default font. <b>Note:</b>you should also add code below to your app theme in styles.xml. </p>     * {@code <item name="android:typeface">monospace</item>}     * <p>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.</p>     * @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));    }    /**     * <p>Replace system default font. <b>Note:</b>you should also add code below to your app theme in styles.xml. </p>     * {@code <item name="android:typeface">monospace</item>}     * <p>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.</p>     */    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();        }    }}

        核心代码在:replaceFont方法,替换TextView的字体,那大家就会疑问了,这个工具类只替换了Textview的字体,那如果用了EditView、RadioButton等呢。大家可以看下那些控件的父类,它们都是继承TextView,这样就豁然开朗,细节果然决定成败啊。整个工具类在字体替换的效率上都有所体现,采用软引用和HashMap缓存策略大大降低替换时的资源消耗,考虑的确很全面,并采用反射机制对Typeface进行设置达到换字体的目的。

这个工具类几乎完美,的确有许多值得学习的地方,比如在单例设置是采用了synchronized   摒弃了懒汉的模式,在资源使用上用到了SoftReference  软引用,在缓存上用了HashMap,最后采用反射赋值,这几点都是可圈可点。如果将缓存的HashMap换成ConcurrentHashMap或许在多线程环境下性能表现会更好些。

下面是该工具类作者的GitHub:https://github.com/whinc

0 0
原创粉丝点击