Можно ли установить собственный шрифт для всего приложения? - PullRequest
261 голосов
/ 26 апреля 2010

Мне нужно использовать определенный шрифт для всего приложения. У меня есть файл .ttf для того же. Можно ли установить его в качестве шрифта по умолчанию при запуске приложения, а затем использовать его в другом месте приложения? Когда установлено, как я могу использовать его в XML-макете?

Ответы [ 24 ]

447 голосов
/ 02 июня 2013

Да, с отражением. Это работает ( на основе этого ответа ):

(Примечание: это обходной путь из-за отсутствия поддержки пользовательских шрифтов, поэтому, если вы хотите изменить эту ситуацию, пожалуйста, отметьте звездочку, чтобы проголосовать за проблему android ). Примечание: Не оставляйте комментарии "Я тоже" по этому вопросу, все, кто смотрел его, получают по электронной почте, когда вы делаете это. Так что просто "пометить звездочку", пожалуйста.

import java.lang.reflect.Field;
import android.content.Context;
import android.graphics.Typeface;

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 e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

Затем необходимо перегрузить несколько шрифтов по умолчанию, например, в приложении класс:

public final class Application extends android.app.Application {
    @Override
    public void onCreate() {
        super.onCreate();
        FontsOverride.setDefaultFont(this, "DEFAULT", "MyFontAsset.ttf");
        FontsOverride.setDefaultFont(this, "MONOSPACE", "MyFontAsset2.ttf");
        FontsOverride.setDefaultFont(this, "SERIF", "MyFontAsset3.ttf");
        FontsOverride.setDefaultFont(this, "SANS_SERIF", "MyFontAsset4.ttf");
    }
}

Или, если вы используете один и тот же файл шрифтов, вы можете улучшить его, загрузив его только один раз.

Однако я, как правило, просто перезаписываю одну, скажем "MONOSPACE", а затем настраиваю стиль, чтобы расширить шрифт приложения:

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

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

API 21 Android 5.0

Я исследовал сообщения в комментариях о том, что он не работает и, похоже, несовместим с темой android:Theme.Material.Light.

Если эта тема не важна для вас, используйте более старую тему, например ::

<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
    <item name="android:typeface">monospace</item>
</style>
64 голосов
/ 02 декабря 2015

В Android есть отличная библиотека для пользовательских шрифтов: Каллиграфия

вот пример того, как его использовать.

в Gradle вам нужно поместить эту строку в файл build.gradle вашего приложения:

dependencies {
    compile 'uk.co.chrisjenx:calligraphy:2.2.0'
}

, а затем создайте класс, расширяющий Application, и напишите этот код:

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        CalligraphyConfig.initDefault(new CalligraphyConfig.Builder()
                        .setDefaultFontPath("your font path")
                        .setFontAttrId(R.attr.fontPath)
                        .build()
        );
    }
} 

и в классе действия поместите этот метод перед onCreate:

@Override
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase));
}

и последнее, что ваш файл манифеста должен выглядеть следующим образом:

<application
   .
   .
   .
   android:name=".App">

и это изменит всю активность на ваш шрифт! это просто и чисто!

47 голосов
/ 13 января 2012

Хотя это не будет работать для всего приложения, оно будет работать для действия и может быть повторно использовано для любого другого действия. Я обновил свой код благодаря @ FR073N для поддержки других видов. Я не уверен в проблемах с Buttons, RadioGroups и т. Д., Поскольку все эти классы расширяют TextView, поэтому они должны работать просто отлично. Я добавил логическое условие для использования отражения, потому что оно кажется очень хакерским и может заметно снизить производительность.

Примечание: как указано, это не будет работать для динамического контента! Для этого можно вызвать этот метод, скажем, методом onCreateView или getView, но это требует дополнительных усилий.

/**
 * Recursively sets a {@link Typeface} to all
 * {@link TextView}s in a {@link ViewGroup}.
 */
public static final void setAppFont(ViewGroup mContainer, Typeface mFont, boolean reflect)
{
    if (mContainer == null || mFont == null) return;

    final int mCount = mContainer.getChildCount();

    // Loop through all of the children.
    for (int i = 0; i < mCount; ++i)
    {
        final View mChild = mContainer.getChildAt(i);
        if (mChild instanceof TextView)
        {
            // Set the font if it is a TextView.
            ((TextView) mChild).setTypeface(mFont);
        }
        else if (mChild instanceof ViewGroup)
        {
            // Recursively attempt another ViewGroup.
            setAppFont((ViewGroup) mChild, mFont);
        }
        else if (reflect)
        {
            try {
                Method mSetTypeface = mChild.getClass().getMethod("setTypeface", Typeface.class);
                mSetTypeface.invoke(mChild, mFont); 
            } catch (Exception e) { /* Do something... */ }
        }
    }
}

Тогда, чтобы использовать его, вы должны сделать что-то вроде этого:

final Typeface mFont = Typeface.createFromAsset(getAssets(),
"fonts/MyFont.ttf"); 
final ViewGroup mContainer = (ViewGroup) findViewById(
android.R.id.content).getRootView();
HomeActivity.setAppFont(mContainer, mFont);

Надеюсь, это поможет.

33 голосов
/ 24 марта 2016

В итоге:

Вариант № 1: Использовать отражение для применения шрифта (объединяя weston & Roger Huang 's answer):

import java.lang.reflect.Field;
import android.content.Context;
import android.graphics.Typeface;

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) {
        if (isVersionGreaterOrEqualToLollipop()) {
            Map<String, Typeface> newMap = new HashMap<String, Typeface>();
            newMap.put("sans-serif", newTypeface);
            try {
                final Field staticField = Typeface.class.getDeclaredField("sSystemFontMap");
                staticField.setAccessible(true);
                staticField.set(null, newMap);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        } else {
            try {
                final Field staticField = Typeface.class.getDeclaredField(staticTypefaceFieldName);
                staticField.setAccessible(true);
                staticField.set(null, newTypeface);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } 
        }
    }

} 

Использование в классе приложения:

public final class Application extends android.app.Application {
    @Override 
    public void onCreate() { 
        super.onCreate(); 
        FontsOverride.setDefaultFont(this, "DEFAULT", "MyFontAsset.ttf");
        FontsOverride.setDefaultFont(this, "MONOSPACE", "MyFontAsset2.ttf");
        FontsOverride.setDefaultFont(this, "SERIF", "MyFontAsset3.ttf");
        FontsOverride.setDefaultFont(this, "SANS_SERIF", "MyFontAsset4.ttf");
    } 
} 

установить стиль для принудительного применения этого шрифта в приложении (на основе lovefish ):

Pre-леденец:

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

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

Леденец (API 21):

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

   <!-- Application theme. -->
   <style name="AppTheme" parent="AppBaseTheme">
       <item name="android:textAppearance">@style/CustomTextAppearance</item>
   </style>

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

Option2: Подкласс каждого просмотра, где вам нужно настроить шрифт, т.е. ListView, EditTextView, Button и т. Д. ( Palani ответ):

public class CustomFontView extends TextView {

public CustomFontView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init(); 
} 

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

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

private void init() { 
    if (!isInEditMode()) {
        Typeface tf = Typeface.createFromAsset(getContext().getAssets(), "Futura.ttf");
        setTypeface(tf);
    } 
} 

Вариант 3: Реализация обходчика вида, проходящего через иерархию видов текущего экрана:

Вариация # 1 ( Tom 'ответ):

public static final void setAppFont(ViewGroup mContainer, Typeface mFont, boolean reflect)
{ 
    if (mContainer == null || mFont == null) return;

    final int mCount = mContainer.getChildCount();

    // Loop through all of the children. 
    for (int i = 0; i < mCount; ++i)
    { 
        final View mChild = mContainer.getChildAt(i);
        if (mChild instanceof TextView)
        { 
            // Set the font if it is a TextView. 
            ((TextView) mChild).setTypeface(mFont);
        } 
        else if (mChild instanceof ViewGroup)
        { 
            // Recursively attempt another ViewGroup. 
            setAppFont((ViewGroup) mChild, mFont);
        } 
        else if (reflect)
        { 
            try { 
                Method mSetTypeface = mChild.getClass().getMethod("setTypeface", Typeface.class);
                mSetTypeface.invoke(mChild, mFont); 
            } catch (Exception e) { /* Do something... */ }
        } 
    } 
} 

Использование:

final ViewGroup mContainer = (ViewGroup) findViewById(
android.R.id.content).getRootView();
HomeActivity.setAppFont(mContainer, Typeface.createFromAsset(getAssets(),
"fonts/MyFont.ttf"));

Вариант № 2: https://coderwall.com/p/qxxmaa/android-use-a-custom-font-everywhere.

Опция № 4: Использовать сторонний Lib под названием Каллиграфия .

Лично я бы порекомендовал вариант № 4, так как он избавляет от многих головных болей.

28 голосов
/ 06 марта 2015

Я хотел бы улучшить ответ weston для API 21 Android 5.0.

Причина

В API 21 большинство стилей текста включают параметр fontFamily, например:

<style name="TextAppearance.Material">
     <item name="fontFamily">@string/font_family_body_1_material</item>
</style>

Который применяет стандартный шрифт Roboto по умолчанию:

<string name="font_family_body_1_material">sans-serif</string>

В исходном ответе не применяется моноширинный шрифт, потому что android: fontFamily имеет больший приоритет по сравнению с атрибутом android: typeface ( ссылка ). Использование Theme.Holo. * Является допустимым обходным решением, поскольку внутри нет настроек android: fontFamily.

Решение

Поскольку Android 5.0 помещает системную гарнитуру в статическую переменную Typeface.sSystemFontMap ( ссылка ), мы можем использовать ту же технику отражения для ее замены:

protected static void replaceFont(String staticTypefaceFieldName,
        final Typeface newTypeface) {
    if (isVersionGreaterOrEqualToLollipop()) {
        Map<String, Typeface> newMap = new HashMap<String, Typeface>();
        newMap.put("sans-serif", newTypeface);
        try {
            final Field staticField = Typeface.class
                    .getDeclaredField("sSystemFontMap");
            staticField.setAccessible(true);
            staticField.set(null, newMap);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    } else {
        try {
            final Field staticField = Typeface.class
                    .getDeclaredField(staticTypefaceFieldName);
            staticField.setAccessible(true);
            staticField.set(null, newTypeface);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
15 голосов
/ 08 ноября 2013

это очень просто ... 1. Загрузите и вставьте свой собственный шрифт в ресурсы. Затем напишите один отдельный класс для представления текста следующим образом: здесь я использовал шрифт futura

public class CusFntTextView extends TextView {

public CusFntTextView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init();
}

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

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

private void init() {
    if (!isInEditMode()) {
        Typeface tf = Typeface.createFromAsset(getContext().getAssets(), "Futura.ttf");
        setTypeface(tf);
    }
}

}

и выполните следующие действия в xml:

 <com.packagename.CusFntTextView
        android:id="@+id/tvtitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"         
        android:text="Hi Android"           
        android:textAppearance="?android:attr/textAppearanceLarge"
      />
9 голосов
/ 09 ноября 2012

Я бы также предложил расширить TextView и другие элементы управления, но было бы лучше рассмотреть возможность установки шрифта в конструкциях.

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

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

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

protected void init() {
    setTypeface(Typeface.createFromAsset(getContext().getAssets(), AppConst.FONT));
}
8 голосов
/ 17 ноября 2015

Здесь можно найти блестящее решение: https://coderwall.com/p/qxxmaa/android-use-a-custom-font-everywhere.

Просто расширьте действия из BaseActivity и напишите эти методы.Также вам следует лучше кэшировать шрифты, как описано здесь: https://stackoverflow.com/a/16902532/2914140.


После некоторых исследований я написал код, который работает на Samsung Galaxy Tab A (Android 5.0).Использовали код Уэстона и Роджера Хуанга, а также https://stackoverflow.com/a/33236102/2914140. Также протестирован на Lenovo TAB 2 A10-70L, где он не работает.Я вставил здесь шрифт «Comic Sans», чтобы увидеть разницу.

import android.content.Context;
import android.graphics.Typeface;
import android.os.Build;
import android.util.Log;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class FontsOverride {
    private static final int BOLD = 1;
    private static final int BOLD_ITALIC = 2;
    private static final int ITALIC = 3;
    private static final int LIGHT = 4;
    private static final int CONDENSED = 5;
    private static final int THIN = 6;
    private static final int MEDIUM = 7;
    private static final int REGULAR = 8;

    private Context context;

    public FontsOverride(Context context) {
        this.context = context;
    }

    public void loadFonts() {
        Map<String, Typeface> fontsMap = new HashMap<>();
        fontsMap.put("sans-serif", getTypeface("comic.ttf", REGULAR));
        fontsMap.put("sans-serif-bold", getTypeface("comic.ttf", BOLD));
        fontsMap.put("sans-serif-italic", getTypeface("comic.ttf", ITALIC));
        fontsMap.put("sans-serif-light", getTypeface("comic.ttf", LIGHT));
        fontsMap.put("sans-serif-condensed", getTypeface("comic.ttf", CONDENSED));
        fontsMap.put("sans-serif-thin", getTypeface("comic.ttf", THIN));
        fontsMap.put("sans-serif-medium", getTypeface("comic.ttf", MEDIUM));
        overrideFonts(fontsMap);
    }

    private void overrideFonts(Map<String, Typeface> typefaces) {
        if (Build.VERSION.SDK_INT == 21) {
            try {
                final Field field = Typeface.class.getDeclaredField("sSystemFontMap");
                field.setAccessible(true);
                Map<String, Typeface> oldFonts = (Map<String, Typeface>) field.get(null);
                if (oldFonts != null) {
                    oldFonts.putAll(typefaces);
                } else {
                    oldFonts = typefaces;
                }
                field.set(null, oldFonts);
                field.setAccessible(false);
            } catch (Exception e) {
                Log.e("TypefaceUtil", "Cannot set custom fonts");
            }
        } else {
            try {
                for (Map.Entry<String, Typeface> entry : typefaces.entrySet()) {
                    final Field staticField = Typeface.class.getDeclaredField(entry.getKey());
                    staticField.setAccessible(true);
                    staticField.set(null, entry.getValue());
                }
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    private Typeface getTypeface(String fontFileName, int fontType) {
        final Typeface tf = Typeface.createFromAsset(context.getAssets(), "fonts/" + fontFileName);
        return Typeface.create(tf, fontType);
    }
}

Чтобы запустить код во всем приложении, вы должны написать в каком-нибудь классе, например Application, следующее:

    new FontsOverride(this).loadFonts();

Создайте папку «шрифты» внутри «ресурсов» и поместите туда нужные шрифты.Простая инструкция может быть найдена здесь: https://stackoverflow.com/a/31697103/2914140.

Устройство Lenovo также неправильно получает значение гарнитуры.В большинстве случаев он возвращает Typeface.NORMAL, иногда ноль.Даже если TextView выделен жирным шрифтом (в макете XML-файла).Смотрите здесь: TextView isBold всегда возвращает NORMAL .Таким образом, текст на экране всегда написан обычным шрифтом, а не жирным шрифтом или курсивом.Так что я думаю, что это ошибка производителя.

8 голосов
/ 28 мая 2015

Я хотел бы улучшить ответы weston и Roger Huang за более чем API 21 Android-леденец с темой " Theme.AppCompat ".

ниже Android 4.4

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

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

Более (равно) API 5.0

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

   <!-- Application theme. -->
   <style name="AppTheme" parent="AppBaseTheme">
       <item name="android:textAppearance">@style/CustomTextAppearance</item>
   </style>

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

И утилитный файл FontsOverride такой же, как и в ответе weston . Я проверил в этих телефонах:

Nexus 5 (основная система Android Android 5.1)

ZTE V5 (Android 5.1 CM12.1)

XIAOMI note (android 4.4 MIUI6)

HUAWEI C8850 (Android 2.3.5 НЕИЗВЕСТНО)

6 голосов
/ 11 апреля 2017

Начиная с Android O это теперь можно определить непосредственно из XML, и моя ошибка теперь закрыта!

Подробнее см. Здесь

TL; DR:

Сначала вы должны добавить свои шрифты в проект

Во-вторых, вы добавляете семейство шрифтов, например:

<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android">
    <font
        android:fontStyle="normal"
        android:fontWeight="400"
        android:font="@font/lobster_regular" />
    <font
        android:fontStyle="italic"
        android:fontWeight="400"
        android:font="@font/lobster_italic" />
</font-family>

Наконец, вы можете использовать шрифт в макете или стиле:

<TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:fontFamily="@font/lobster"/>

<style name="customfontstyle" parent="@android:style/TextAppearance.Small">
    <item name="android:fontFamily">@font/lobster</item>
</style>

Наслаждайтесь!

...