替換整個APP字體--修改全局字體樣式

原文地址java

首先將項目須要的字體資源放置在app下:android

注意,字體ttf文件只能用英文字母,中文會報找不到文件異常。app

未設置以前的佈局樣式: ide

字體文件準備好後,咱們就能夠按需設置本身想要的字體樣式。下面提供了3種設置方法,這3種方法均可以改變能夠顯示文本的控件字體樣式,如TextView,Button,EditText,CheckBox,RadioButton等等:工具

方法1:自定義控件 FontTextView

重寫TextView,重寫其中的setTypeface方法,設置本身的字體樣式佈局

package com.guorentong.learn.myapplication;

import android.content.Context;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.widget.TextView;

public class FontTextView extends android.support.v7.widget.AppCompatTextView {

    public FontTextView(Context context) {
        super(context);
    }

    public FontTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FontTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private Typeface createTypeface(Context context, String fontPath) {
        return Typeface.createFromAsset(context.getAssets(), fontPath);
    }


    @Override
    public void setTypeface(Typeface tf, int style) {
        super.setTypeface(createTypeface(getContext(),"fonts/pop.ttf"), style);
    }
}

佈局中直接用自定義的FontTextView類便可。性能

<com.guorentong.learn.myapplication.FontTextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="這是一個TextView"/>

<Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="這是一個Button"/>
    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="這是一個EditText"/>
    <CheckBox
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="這是一個CheckBox"/>
    <RadioButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="這是一個RadioButton"/>

這種設置方式的優缺點: 
優勢:使用簡單方便,不須要額外的工做。 
缺點:只能替換一類控件的字體,若是須要替換Button或EditText控件的字體,須要以相同的方式自定義這些控件,這樣工做量大。字體

方法2:遞歸批量替換某個View及其子View的字體

Android中可顯示文本的控件都直接或間接繼承自TextView,批量替換字體的原理就是從指定的View節點開始遞歸遍歷全部子View,若是子View類型是TextView類型或其子類型則替換字體,若是子View是ViewGroup類型則重複這一過程。我抽取了一個工具類,代碼以下:this

package com.guorentong.learn.myapplication;

import android.app.Activity;
import android.content.Context;
import android.graphics.Typeface;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class TypefaceUtil {

    /**
     * <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 static void replaceFont(@NonNull View root, String fontPath) {
        if (root == null || TextUtils.isEmpty(fontPath)) {
            return;
        }
        
        if (root instanceof TextView) { // If view is TextView or it's subclass, replace it's font
            TextView textView = (TextView)root;
            int style = Typeface.NORMAL;
            if (textView.getTypeface() != null) {
                style = textView.getTypeface().getStyle();
            }
            textView.setTypeface(createTypeface(root.getContext(), fontPath), 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), fontPath);
            }
        }
    }

    /**
     * <p>Replace the font of specified view and it's children</p>
     * @param context The view corresponding to the activity.
     * @param fontPath font file path relative to 'assets' directory.
     */
    public static void replaceFont(@NonNull Activity context, String fontPath) {
        replaceFont(getRootView(context),fontPath);

        Log.e("TAG", "fontPath: --------------------"+fontPath );
    }

    /*
     * Create a Typeface instance with your font file
     */
    public static Typeface createTypeface(Context context, String fontPath) {
        return Typeface.createFromAsset(context.getAssets(), fontPath);
    }

    /**
     * 從Activity 獲取 rootView 根節點
     * @param context
     * @return 當前activity佈局的根節點
     */
    public static View getRootView(Activity context)
    {
        return ((ViewGroup)context.findViewById(android.R.id.content)).getChildAt(0);
    }

}

代碼中調用,這裏建議最好放在BaseActivity中,由於this只能表明當前頁面,放在BaseActivity中讓全部Activity繼承自BaseActivity能夠全局的改變字體樣式:spa

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayoutId());
        //設置字體改變
        TypefaceUtil.replaceFont(this, "fonts/pop.ttf");
    }

這種設置方式的優缺點: 
優勢:不須要修改XML佈局文件,不須要重寫控件,能夠批量替換全部繼承自TextView的控件的字體,適合須要批量替換字體的場合,如程序的默認字體。 
缺點:若是要替換整個App的全部字體,須要在每一個有界面的地方批量替換一次,頁面多了仍是有些工做量的,不過能夠在Activity和Fragment的基類中完成這個工做。其次,性能可能差一點,畢竟要遞歸遍歷全部子節點(不過實際使用中沒有明顯的性能降低程序依然流暢)。

 

方法3:經過反射替換默認字體

App中顯示的字體來自於Typeface中的預約義的字體,這種方式是經過改變系統字體樣式改變字體。

首先須要改變APP的BaseTheme

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    <!-- Set system default typeface -->
    <item name="android:typeface">monospace</item>
</style>

再而後我將須要的方法又抽取了一下,和以前的TypefaceUtil造成了一個完整的工具類,代碼以下:

package com.guorentong.learn.myapplication;

import android.app.Activity;
import android.content.Context;
import android.graphics.Typeface;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.lang.reflect.Field;

public class TypefaceUtils {
    /**
     * 爲給定的字符串添加HTML紅色標記,當使用Html.fromHtml()方式顯示到TextView 的時候其將是紅色的
     *
     * @param string 給定的字符串
     * @return
     */
    public static String addHtmlRedFlag(String string) {
        return "<font color=\"red\">" + string + "</font>";
    }

    /**
     * 將給定的字符串中全部給定的關鍵字標紅
     *
     * @param sourceString 給定的字符串
     * @param keyword      給定的關鍵字
     * @return 返回的是帶Html標籤的字符串,在使用時要經過Html.fromHtml()轉換爲Spanned對象再傳遞給TextView對象
     */
    public static String keywordMadeRed(String sourceString, String keyword) {
        String result = "";
        if (sourceString != null && !"".equals(sourceString.trim())) {
            if (keyword != null && !"".equals(keyword.trim())) {
                result = sourceString.replaceAll(keyword, "<font color=\"red\">" + keyword + "</font>");
            } else {
                result = sourceString;
            }
        }
        return result;
    }

    /**
     * <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 static void replaceFont(@NonNull View root, String fontPath) {
        if (root == null || TextUtils.isEmpty(fontPath)) {
            return;
        }


        if (root instanceof TextView) { // If view is TextView or it's subclass, replace it's font
            TextView textView = (TextView)root;
            int style = Typeface.NORMAL;
            if (textView.getTypeface() != null) {
                style = textView.getTypeface().getStyle();
            }
            textView.setTypeface(createTypeface(root.getContext(), fontPath), 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), fontPath);
            }
        }
    }

    /**
     * <p>Replace the font of specified view and it's children</p>
     * 經過遞歸批量替換某個View及其子View的字體改變Activity內部控件的字體(TextView,Button,EditText,CheckBox,RadioButton等)
     * @param context The view corresponding to the activity.
     * @param fontPath font file path relative to 'assets' directory.
     */
    public static void replaceFont(@NonNull Activity context, String fontPath) {
        replaceFont(getRootView(context),fontPath);
    }


    /*
     * Create a Typeface instance with your font file
     */
    public static Typeface createTypeface(Context context, String fontPath) {
        return Typeface.createFromAsset(context.getAssets(), fontPath);
    }

    /**
     * 從Activity 獲取 rootView 根節點
     * @param context
     * @return 當前activity佈局的根節點
     */
    public static View getRootView(Activity context)
    {
        return ((ViewGroup)context.findViewById(android.R.id.content)).getChildAt(0);
    }

    /**
     * 經過改變App的系統字體替換App內部全部控件的字體(TextView,Button,EditText,CheckBox,RadioButton等)
     * @param context
     * @param fontPath
     * 須要修改style樣式爲monospace:
     */
//    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
//    <!-- Customize your theme here. -->
//    <!-- Set system default typeface -->
//    <item name="android:typeface">monospace</item>
//    </style>
    public static void replaceSystemDefaultFont(@NonNull Context context, @NonNull String fontPath) {
        replaceTypefaceField("MONOSPACE", createTypeface(context, fontPath));
    }

    /**
     * <p>Replace field in class Typeface with reflection.</p>
     */
    private static void replaceTypefaceField(String fieldName, Object value) {
        try {
            Field defaultField = Typeface.class.getDeclaredField(fieldName);
            defaultField.setAccessible(true);
            defaultField.set(null, value);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

代碼中引用只須要在BaseApplication的onCreate裏設置:

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //設置字體改變
        TypefaceUtils.replaceSystemDefaultFont(this,"fonts/pop.ttf");
        setContentView(getLayoutId());
    }

這種設置方式的優缺點: 
優勢:方式2的優勢+更加簡潔 
缺點:字體文件通常比較大,加載時間長並且佔內存(不過實際使用中沒有明顯的性能降低程序依然流暢)。

 

 

我的中心設置

我通常都是用第2,3種,簡潔高效,如今說一下如何在我的設置裏邊改變你的app字體: 
經實踐,第2種方法是最好的,能夠實時更新頁面。而第三種須要返回從新進入到activity纔會看到效果。

先在BaseActivity註冊一個字體改變監聽的廣播

package com.guorentong.learn.myapplication;

import android.app.Activity;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

public abstract class BaseActivity extends AppCompatActivity {

    private TypefaceChangeReceiver typefaceChangeReceiver;
    public static Context context;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        TypefaceUtils.replaceSystemDefaultFont(this,"fonts/pop.ttf");
        setContentView(getLayoutId());
//        TypefaceUtil.replaceFont(this, "fonts/pop.ttf");

        //改變新建立Activity的字體
        onTypefaceChange(PreferencesUtil.getString(ConstantValue.AppSetting.SHPNAME,ConstantValue.AppSetting.typeface));
        typefaceChangeReceiver = new TypefaceChangeReceiver();
        IntentFilter typefaceFilter = new IntentFilter();
        typefaceFilter.addAction(ConstantValue.ReceiverAction.TYPEFACE_ACTION);
        registerReceiver(typefaceChangeReceiver,typefaceFilter);
        initData();
    }

    /**
     * 設置當前activity的layout
     * @return 當前界面的佈局id
     */
    protected abstract int getLayoutId();
    protected abstract void initData();

    @Override
    protected void onDestroy() {
        unregisterReceiver(typefaceChangeReceiver);
        super.onDestroy();
    }

    /**
     * 字體改變
     */
    protected void onTypefaceChange(String typeface){
        //第2種
//        TypefaceUtil.replaceFont(this, typeface);
        //第3種
        TypefaceUtils.replaceSystemDefaultFont(this,typeface);
    }

    /**
     * 字體改變監聽,用於改變整個APP字體
     */
    public class TypefaceChangeReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if(ConstantValue.ReceiverAction.TYPEFACE_ACTION.equals(intent.getAction())){
                String typeface = intent.getStringExtra("typeface");
                //改變未銷燬尚存在的Activity的字體
                onTypefaceChange(typeface);
            }
        }
    }
}

常量抽取出來,放到ConstantValue常量類中:

package com.guorentong.learn.myapplication;

public class ConstantValue {

    interface AppSetting{
        String SHPNAME = "SETTING";
        String typeface = "typeface";
    }

    interface ReceiverAction{
        String TYPEFACE_ACTION = "font.TYPEFACE_CHANGE";
    }

    interface Typeface{
        String POP = "fonts/pop.ttf";//pop
        String FZZY = "fonts/fzzy.TTF";//方正準圓
        String HWCY = "fonts/hwcy.TTF";//華文彩雲
        String HWXK = "fonts/hwxk.ttf";//華文行楷
        String HWXS = "fonts/hwxs.ttf";//華文新宋
        String HWXW = "fonts/hwxw.TTF";//華文新魏
        String YY = "fonts/yy.ttf";//幼圓
    }
}

在SettingActivity裏發送特定的廣播便可:

@Override
public void onClick(View v) {

    Intent intent = new Intent(ConstantValue.ReceiverAction.TYPEFACE_ACTION);
    String typeface = null;
    switch (v.getId()) {
        default:
            break;
        case R.id.but0:
            typeface = ConstantValue.Typeface.POP;
            break;
        case R.id.but1:
            typeface = ConstantValue.Typeface.FZZY;
            break;
        case R.id.but2:
            typeface = ConstantValue.Typeface.HWCY;
            break;
        case R.id.but3:
            typeface = ConstantValue.Typeface.HWXK;
            break;
        case R.id.but4:
            typeface = ConstantValue.Typeface.HWXS;
            break;
        case R.id.but5:
            typeface = ConstantValue.Typeface.HWXW;
            break;
        case R.id.but6:
            typeface = ConstantValue.Typeface.YY;
            break;
    }
    //保存字體設置
    PreferencesUtil.put(ConstantValue.AppSetting.SHPNAME, ConstantValue.AppSetting.typeface, typeface);
    intent.putExtra("typeface", typeface);
    sendBroadcast(intent);
}

這裏有兩個動做, 
一個是發送廣播,用於修改以前建立了但並未銷燬的Activity的字體; 
另外一個是保存設置的字體,用於修改以後將建立的Activity的字體。 

PreferencesUtil的一部分代碼:

package com.guorentong.learn.myapplication;

import android.content.Context;
import android.content.SharedPreferences;
import android.support.annotation.NonNull;

import java.util.Set;

public class PreferencesUtil {
    private static Context context = BaseApplication.getContext();
    private final static String DEFAULT_STRING_VALUE = "";

    public static void put(@NonNull String SHPNAME, @NonNull String key, Object value){
        SharedPreferences shp = context.getSharedPreferences(SHPNAME, Context.MODE_PRIVATE);
        SharedPreferences.Editor edit = shp.edit();
        if(value instanceof String)
            edit.putString(key,(String)value);
        if(value instanceof Boolean)
            edit.putBoolean(key, (Boolean) value);
        if(value instanceof Float)
            edit.putFloat(key, (Float) value);
        if(value instanceof Long)
            edit.putLong(key, (Long) value);
        if(value instanceof Integer)
            edit.putInt(key, (Integer) value);
        if(value instanceof Set)
            edit.putStringSet(key, (Set<String>) value);
        edit.apply();
    }

    public static String getString(@NonNull String SHPNAME,@NonNull String key){
        SharedPreferences shp = context.getSharedPreferences(SHPNAME, Context.MODE_PRIVATE);
        return shp.getString(key, DEFAULT_STRING_VALUE);
    }
}

這樣便實現了在一個SettingActivity裏改變全局字體的功能。

使用注意: 
1.若是字體文件比較大,當設置後可能並不會當即生效,有1~2s的延遲,具體還依據類中控件的數量來定。 
2.相當重要,全部的Activity請務必要繼承BaseActivity。

一、在清單文件裏註冊

android:allowBackup="true"
android:name="BaseApplication"
public class BaseApplication extends Application {
    private static BaseApplication mApplication;

    @Override
    public void onCreate() {
        super.onCreate();
        mApplication = this;
    }

    public static Context getContext(){
        return mApplication;
    }
}
相關文章
相關標籤/搜索