1. 程式人生 > >太激動!Android修改全域性字型樣式,替換整個APP字型

太激動!Android修改全域性字型樣式,替換整個APP字型

最近一直在如何全域性修改app字型上困惑著,今天終於有了突破。我將蒐集的資料進行了整理,現在提供給大家。

前面為分析,建議直接翻到最後看【個人中心設定】。

首先將專案需要的字型資源放置在app下:
放置需要的字型檔案

這是我自己找的字型檔案,分別代表粗體,方正準圓,華文彩雲,華文行楷,華文新宋,華文新魏,幼圓。
注意,字型ttf檔案只能用英文字母,中文會報找不到檔案異常。

我們先看看未設定之前的佈局樣式:
原始圖片
字型檔案準備好後,我們就可以按需設定自己想要的字型樣式。下面提供了3種設定方法,這3種方法都可以改變可以顯示文字的控制元件字型樣式,如TextView,Button,EditText,CheckBox,RadioButton等等:

方法1:自定義控制元件 FontTextView

重寫TextView,重寫其中的setTypeface方法,設定自己的字型樣式

package com.laundrylang.laundrylangpda.view;
public class FontTextView extends TextView{

    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/fzzy.ttf"), style); } }

佈局中直接用自定義的FontTextView類即可。

<com.laundrylang.laundrylangpda.view.FontTextView
        android:layout_width="wrap_content"
        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"/>

前後對照佈局檔案,因為這裡只重寫了TextView,所以只有TextView有變化:
第一種方式設定

這種設定方式的優缺點:
優點:使用簡單方便,不需要額外的工作。
缺點:只能替換一類控制元件的字型,如果需要替換Button或EditText控制元件的字型,需要以相同的方式自定義這些控制元件,這樣工作量大。

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

Android中可顯示文字的控制元件都直接或間接繼承自TextView,批量替換字型的原理就是從指定的View節點開始遞迴遍歷所有子View,如果子View型別是TextView型別或其子型別則替換字型,如果子View是ViewGroup型別則重複這一過程。我抽取了一個工具類,程式碼如下:

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);
    }


    /*
     * 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可以全域性的改變字型樣式:

TypefaceUtil.replaceFont(this, "fonts/fzzy.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.laundrylang.laundrylangpda.util;

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;

/*
 * Copyright (C) 2013 Peng fei Pan <[email protected]>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
public class TypefaceUtil {
    /**
     * 為給定的字串新增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裡設定,為了增強效果,這裡我將顯示樣式變成粗體:

TypefaceUtil.replaceSystemDefaultFont(this,"fonts/bold.ttf");

前後對照佈局檔案,可以看到,雖然是粗體,但和方法2一樣,也是所有的字型都更換樣式了:
第三種方式設定

這種設定方式的優缺點:
優點:方式2的優點+更加簡潔
缺點:字型檔案一般比較大,載入時間長而且佔記憶體(不過實際使用中沒有明顯的效能下降程式依然流暢)。

個人中心設定

我一般都是用第2,3種,簡潔高效,現在說一下如何在個人設定裡邊改變你的app字型:
經實踐,第2種方法是最好的,可以實時更新頁面。而第三種需要返回重新進入到activity才會看到效果。

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

package com.laundrylang.laundrylangpda.activity;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

import com.laundrylang.laundrylangpda.constant.ConstantValue;
import com.laundrylang.laundrylangpda.util.PreferencesUtil;
import com.laundrylang.laundrylangpda.util.TypefaceUtil;

public abstract class BaseActivity extends FragmentActivity {
    private TypefaceChangeReceiver typefaceChangeReceiver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayout());
        //改變新建立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);
    }

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

    /**
     * 設定當前activity的layout
     * @return 當前介面的佈局id
     */
    protected abstract int getLayout();

    /**
     * 字型改變
     */
    protected void onTypefaceChange(String typeface){
        TypefaceUtil.replaceFont(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常量類中:

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

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

    interface Typeface{
        String BOLD = "fonts/bold.ttf";//粗體
        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()){
            case R.id.bold:
                typeface = ConstantValue.Typeface.BOLD;
                break;
            case R.id.hwcy:
                typeface = ConstantValue.Typeface.HWCY;
                break;
        }
        //儲存字型設定
        PreferencesUtil.put(ConstantValue.AppSetting.SHPNAME, ConstantValue.AppSetting.typeface, typeface);
        intent.putExtra("typeface", typeface);
        sendBroadcast(intent);
    }

這裡有兩個動作,
一個是傳送廣播,用於修改之前建立了但並未銷燬的Activity的字型;
另一個是儲存設定的字型,用於修改之後將建立的Activity的字型。
不過需要在BaseActivity裡setContentView之後新增程式碼:

onTypefaceChange(PreferencesUtil.getString(ConstantValue.AppSetting.SHPNAME,ConstantValue.AppSetting.typeface));

順便附上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修改全域性字型樣式替換整個APP字型

最近一直在如何全域性修改app字型上困惑著,今天終於有了突破。我將蒐集的資料進行了整理,現在提供給大家。 前面為分析,建議直接翻到最後看【個人中心設定】。 首先將專案需要的字型資源放置在app下: 這是我自己找的字型檔案,分別代表粗體,方正準圓,華

Android設定全域性字型大小實現小中大字型功能

很多app有這種需求,實現字型小中大字型設定,仿照QQ的字型。經過測試,下面程式碼完美實現,需要重啟APP才能生效。 上核心程式碼 public class MainActivity extends Activity { @InjectView(R.id.btn

放心嗨Android 9 Pie發布網易雲易盾加固已第一時間適配

工作 工程師 ffffff 谷歌 時間 water 科技 RoCE image 自5月份谷歌在年度開發者大會上首次亮相Android 9 P後,其正式版Android 9 Pie昨天正式發布,網易雲易盾移動安全已在昨天第一時間裏進行了最終適配。 早在今年初,易盾就做好了相關

AI 程式設計師 80 萬BAT 得不到他就用錢搶他

【CSDN編者按】最近一則熱搜把AI程式設計師又送到了臺前,AI程式設計師應屆生80萬了!就在大家議論紛紛改行業時,筆者瞭解到在國外隨著矽谷人工智慧人才的激烈爭奪,薪酬更貴! 據外媒Business Insider報道,為了招募人工智慧領域的頂尖人才,Oracle開出了高

AI程式設計師80萬BAT得不到他就用錢搶他

點選上方“程式人生”,選擇“置頂公眾號” 第一時間關注程式猿(媛)身邊的故事 【CSDN 編者按】最近一則熱搜把AI程式設計師又送到了臺前,AI程式設計師應屆生80萬了!就在大家議論紛紛改行業時,筆者瞭解到在國外隨著矽谷人工智慧人才的激烈爭奪,薪酬更貴!據外媒 Busines

修改select預設樣式相容IE9

前面有篇文章已經提供瞭如何修改select標籤的預設樣式,但是隻能相容到ie10,要相容ie9只能模擬一個類似的 html結構: <div class="select_diy">                                     <s

樣式問題-如何一次性設定網站英文字型樣式中文字型樣式

今天才發現,CSS 的 font-family 屬性 的基本能力之一就是依其列表內字型的排序(優先順序)來顯示文字。  如果設定為「font-family: "英文字型", "中文字型", generic-family;」,就用第一項 "Western Font" 顯示西文(英文字母、英文標點、阿拉伯數

CSS筆記(字型樣式文字屬性和顏色樣式

1 字型樣式 1.1 字體系列(font family) 在CSS中,字型劃分為 五組“字體系列”,分別為: sans-serif字體系列:沒有襯線的字型,最常用不為過,如 Arial、Verdana,Arial Black,Geneva等 seri

Android自定義SeekBar樣式遇到的進度條高度問題

首先來看我的android:progressDrawable="@drawable/seek_progress_drawable",下面是seek_progress_drawable檔案: <

MFC下如何設定控制元件的字型樣式視窗背景和控制元件底色透明

重寫OnPaint方法(WM_PAINT)和OnCtlColor的方法(WM_CTLCOLOR) void CPannelRecentUsed::OnPaint() { CPaintDC dc(this); // device context for painting

intelliJ IDEA (JetBrains PyCharm)中 3個地方設定字型大小文字編輯的字型大小介面字型大小顯示log的字型大小

在使用這個intelliJ IDEA (JetBrains PyCharm)編輯器的時候,可能剛剛開始要設定合適的字型大小,但是除了設定,編輯程式碼時的文字的字型大小外。 在現實console,就是那個log框的字型也是很小。這個地方的設定,也不是一下兩下就能找到的。

℃江讓您從精通到入門:Android Studio 簡單實現ViewPager可做APP操作提示

前期準備,如下圖: 第一步、先書寫佈局檔案:activity_main.xml檔案如下: <?xml version="1.0" encoding="utf-8"?> <Re

Android 切換系統語言後重啟App

問題描述:App->改變系統語言->重進App後,最近的Activity會走onCreate()方法,然後App被殺掉;直到第二次進入App後,App會重啟 解決方案:App->改變系統語言->重進App後,最近的Activity走到onCreate

android:修改PagerTabStrip中的背景顏色標題字型樣式、顏色和圖示以及指示條的顏色

1.修改PagerTabStrip中的背景顏色 我們在佈局中直接設定background屬性即可: <android.support.v4.view.ViewPager android:id="@+id/pager" android:layout_width="fill_parent" andro

Android 修改源碼自定義SwipeRefreshLayout樣式——高仿微信朋友圈下拉刷新

樣式 post and 微信 修改 size roi 自定義 details 修改源碼自定義SwipeRefreshLayout樣式——高仿微信朋友圈下拉刷新Android 修改源碼自定義SwipeRefreshLayout樣式——高仿微信朋友圈下拉

Android左邊控制檯字型樣式設定

file->setting裡面:appearance,紅框裡面設定。(https://img-blog.csdn.net/20171027101134428?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbnp3MzE=/font/5a6L5

Latex修改全域性字型

如果使用的是XeTeX 或者 LuaTeX編譯器的話,可以直接使用fontspec巨集包。用\setmainfont引入就可以了。 但如果使用的是pdfLatex的話就不適用了,此時修改字型只能針對latex自帶的三個字型族,詳情可以看這裡. 引用原文如下: Thes

無奈Redis 作者被迫修改 master-slave 架構的描述

相信在座各位的開發者都不會對 Redis 的主從模式(master-slave)感到陌生。Redis 中的主從模式事實上也是源自 MySQL 中同名的一個概念,是同步資料的一種手段。在這樣的場景下,master-slave 本來是一個不帶任何感情色彩的詞語。然而有很多開發者卻

(五)微信小程式-文章列表-實現及在全域性樣式表新增頁面預設字型樣式

文章列表 每篇文章包含文章標題、文章頭圖、文章概要、評論數和閱讀數,基本上使用view, image, text 這三個元件就可以完成 先將準備好的圖片放在根目錄images檔案相應的路徑下,沒有建立,不過多解釋 我們在前序博文微信輪播圖實現專案下繼續操作操作 在post.wxm

修改 input中的placeholder的字型樣式和顏色

placeholder屬性是css3中新增加的屬性, 由於是新加入的屬性因此對各大瀏覽器都不相容: 因此在使用的時候要加相容性     火狐:-moz-placeholder { /* Mozilla Firefox 4 to&nb