1. 程式人生 > >Android開發 為應用設定自定義字型

Android開發 為應用設定自定義字型

在應用開發中,或許你會聽到設計獅和產品錦鯉這樣抱怨,安卓原生的字型太醜啦,傻大笨粗啊,有沒有辦法換成細體啊…?不幸的是,安卓字型確實傻大笨粗,其次,只有normal和bold,沒有light啊.如果更不幸的是,你看不上安卓可以切換的幾種字型,一定要使用自己的字型檔案時,該怎麼辦呢?下面介紹三種方法,讓應用換上新的字型.

(注: 前兩種方法都有侷限性,想要替換整個應用字型的,直接看第三種方法, 對Android Lollipop同樣支援)

  • 方法一: 自定義view
    1. 首先, 把你的字型檔案放在src/main/assets/fonts 下( /fonts 目錄手動新建)
    2. 自定義view繼承TextView, 使用自定義view就可以啦
public class CustomFontTextView extends TextView {

    public CustomFontTextView(Context context) {
        super(context);
        init(context);
    }

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

    public
CustomFontTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } public void init(Context context) { Typeface newFont = Typeface.createFromAsset(context.getAssets(), "fonts/MyFont.ttf"); setTypeface(newFont); } }
  • 方法二: 遍歷viewGroup
    我們會發現,第一種方法其實只適用於區域性位置修改字型,例如整個佈局頁面都像要設定自定義字型怎麼辦呢?我們可以這樣做:
   public class FontHelper {

    public static final void setAppFont(ViewGroup mContainer, Typeface mFont, boolean reflect) {
        if (mContainer == null || mFont == null) return;
        for (int i = 0; i < mContainer.getChildCount(); ++i) {
            final View mChild = mContainer.getChildAt(i);
            if (mChild instanceof TextView) {
                ((TextView) mChild).setTypeface(mFont);
            } else if (mChild instanceof EditText) {
                ((EditText) mChild).setTypeface(mFont);
                //你可以在這裡新增自定義字型的其他型別的view
            } else if (mChild instanceof ViewGroup) {
                setAppFont((ViewGroup) mChild, mFont, true);
            } else if (reflect) {
                try {
                    Method mSetTypeface = mChild.getClass().getMethod("setTypeface", Typeface.class);
                    mSetTypeface.invoke(mChild, mFont);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

例如我們的佈局檔案是這樣的:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="字型 123 ABC"
        android:textColor="@android:color/black"
        android:textSize="40dp"/>

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="字型 123 ABC"
        android:textColor="@android:color/black"
        android:textSize="40dp"/>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="字型 123 ABC"
            android:textColor="@android:color/black"
            android:textSize="40dp"/>

        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="字型 123 ABC"
            android:textColor="@android:color/black"
            android:textSize="40dp"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="字型 123 ABC"
            android:textColor="@android:color/black"
            android:textSize="40dp"
            android:typeface="sans"/>

    </LinearLayout>

</LinearLayout>

我們可以這樣修改字型:

    final Typeface mFont = Typeface.createFromAsset(getAssets(),
                "fonts/Roboto-Light.ttf");
    final ViewGroup mContainer = (ViewGroup) findViewById(
                android.R.id.content).getRootView();
    FontHelper.setAppFont(mContainer, mFont, true);
  • 方法三: 全域性對映替換

其實這個方法才是我們絕大多數數時候想要用的, 一次性替換應用全部字型.
由於官方並沒有提供相應的介面,所以這裡我們採用的方式時通過反射,獲取Typeface類的靜態成員變數,並修改對應的字型檔案.

這裡根據你使用的主題和API的不同,處理方式也有所區別:

  • 非Theme.Material主題

比如你使用的是Theme.Holo主題,或Theme.AppCompat主題, 首先定義一個工具:

public final class FontsOverride {

    public static void setDefaultFont(Context context,
                                      String staticTypefaceFieldName, String fontAssetName) {
        final Typeface regular = Typeface.createFromAsset(context.getAssets(),
                fontAssetName);
        replaceFont(staticTypefaceFieldName, regular);
    }


    protected static void replaceFont(String staticTypefaceFieldName, 
                                        final Typeface newTypeface) {
        try {
            final Field staticField = Typeface.class
                        .getDeclaredField(staticTypefaceFieldName);
            staticField.setAccessible(true);
            staticField.set(null, newTypeface);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
    }
}

然後在主題裡指定使用某種字型(android lollipop設定有區別):

values

<resources>
    <style name="AppBaseTheme" parent="Theme.AppCompat.Light"></style>

    <!-- Application theme. -->
    <style name="AppTheme" parent="AppBaseTheme">
        <item name="android:typeface">monospace</item>
    </style>
</resources>

values-v21

<resources>
    <style name="AppTheme" parent="Theme.AppCompat.Light">
        <item name="android:textAppearance">@style/CustomTextAppearance
        </item>
    </style>

    <style name="CustomTextAppearance">
        <item name="android:typeface">monospace</item>
    </style>
</resources>

最後在你的Application的onCreate()方法裡面使用:

FontsOverride.setDefaultFont(this, "MONOSPACE", "fonts/MyFont.ttf");
  • 使用Theme.Material主題
    Theme.Material主題是Android 5.0新增的主題
public final class FontsOverride {

    public static void setDefaultFont(Context context,
                                      String staticTypefaceFieldName, String fontAssetName) {
        final Typeface regular = Typeface.createFromAsset(context.getAssets(),
                fontAssetName);
        replaceFont(staticTypefaceFieldName, regular);
    }


    protected static void replaceFont(String staticTypefaceFieldName,
                                      final Typeface newTypeface) {
        //android 5.0及以上我們反射修改Typeface.sSystemFontMap變數
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Map<String, Typeface> newMap = new HashMap<>();
            newMap.put(staticTypefaceFieldName, newTypeface);
            try {
                final Field staticField = Typeface.class
                        .getDeclaredField("sSystemFontMap");
                staticField.setAccessible(true);
                staticField.set(null, newMap);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
        } else {
            try {
                final Field staticField = Typeface.class
                        .getDeclaredField(staticTypefaceFieldName);
                staticField.setAccessible(true);
                staticField.set(null, newTypeface);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }

    }
}
//Material主題預設字型為sans-serif,暫時未找到自定義修改字型的方法
FontsOverride.setDefaultFont(this, "sans-serif", "fonts/Roboto-Light.ttf");
  • 其他方法: 如使用第三方庫Calligraphy, 我沒使用過,如果有感興趣的可以自己嘗試一下.

有需要的可以參考原始碼

安卓應用自定義字型部分就先說到這啦,歡迎大家和我一起討論~