День добрый! В данной статье я хочу обсудить такой важный вопрос как шрифты в Android. Мы создадим свой TextView с возможностью добавлять шрифты в xml и визуально их отображать в превью. Так же решим одну важную проблему – использование шрифтов в списке, без глюков и напрягов для братьев наших меньших, наших Android-устройств.

Дальше я распишу как создать папку assets и добавлять свои шрифты, так что это можно пропустить.

Создание папки assets и добавления шрифтов
Папка assets нужна для хранения самых разнообразных ресурсов в том числе и шрифтов. Создать ее можно либо вручную в корне main:
\app\src\main\assets
Либо более простым способом
image

Дальше файлы с форматом .ttf закидываем в assets либо в корень, либо создаем папку fonts, так как assets поддерживает вложенность.

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

  Typeface type = Typeface.createFromAsset(getAssets(),"fonts/font1.ttf"); 
  myTextView.setTypeface(type);

Когда дело касается более чем пяти TextView, этот процесс начинает раздражать. Но раздражение — это не единственная проблема с которой встречается разработчик, использующий данную конструкцию. Дело в том, что сама обработка файла шрифта и превращение его в объект Typeface довольно трудоемкий процесс, следовательно, использование подобной конструкции в листе вызовет неимоверные глюки.

Данную задачу я предлагаю решить обычным сингтоном. И так создаем что-то похожее:

  
public class SingletonFonts {
    private static Typeface font1;
    private static Typeface font2;
    private static Typeface font3;

    public Typeface getFont1() {
        return font1;
    }

    public  Typeface getFont2() {
        return font2;
    }

    public Typeface getFont3() {
        return font3;
    }


    public static void setFont1(Typeface font1) {
        SingletonFonts.font1 = font1;
    }

    public static void setFont2(Typeface font2) {
        SingletonFonts.font2 = font2;
    }

    public static void setFont3(Typeface font3) {
        SingletonFonts.font3 = font3;
    }

    private static volatile SingletonFonts instance;

    private SingletonFonts() {}

    public static SingletonFonts getInstance(Context activity) {
        SingletonFonts localInstance = instance;
        if (localInstance == null) {
            synchronized (SingletonFonts.class) {
                localInstance = instance;
                if (localInstance == null) {
                    instance = localInstance = new SingletonFonts();
                }
            }
            setFont1(Typeface.createFromAsset(activity.getAssets(), "fonts/font1.ttf"));
            setFont2(Typeface.createFromAsset(activity.getAssets(), "fonts/font2.ttf"));
            setFont3(Typeface.createFromAsset(activity.getAssets(), "fonts/font3.ttf"));

        }
        return localInstance;
    }


}

И устанавливаем шрифты используя синглтон, вот так:

public class MainActivity extends AppCompatActivity {
    TextView textView1;
    TextView textView2;
    TextView textView3;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView1 = (TextView) findViewById(R.id.text_view_1);
        textView2 = (TextView) findViewById(R.id.text_view_2);
        textView3 = (TextView) findViewById(R.id.text_view_3);

        textView1.setTypeface(SingletonFonts.getInstance(this).getFont1());
        textView2.setTypeface(SingletonFonts.getInstance(this).getFont2());
        textView3.setTypeface(SingletonFonts.getInstance(this).getFont3());

    }
}

Используя данную конструкцию мы решили вторую проблему, по поводу лагов в листе, но не первую, самую важную, вам все так же нужно находить все текстовые поля и на все снова и снова сетить шрифты, писать отдельные методы и терять нервные клетки. Еще одна проблема в том, что в привью — стандартный шрифт, следовательно ширину, высоту своего шрифта. вы можете увидеть только на живом девайсе или эмуляторе, это очень не удобно.

Введу выше указанных проблем мы сейчас напишем свой TextView с блэкджеком и шрифтами.

Первым делом создаем класс наследник обычного TextView с конструкторами:

public class CustomFontsTextView extends TextView {
    public CustomFontsTextView(Context context) {
        super(context);
    }

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

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

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

}

Дальше создаём в папке values файл attrs.xml

Создание attrs.xml
Идем вот сюда

image

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

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
</resources>


В нем создаем следующий блок кода:

  //имя вашего класса 
    <declare-styleable name="CustomFontsTextView">
        // перечень всех ваших шрифтов. name = "fonts" - название xml атрибута
        <attr name="fonts" format="enum">
            <enum name="font1" value="0"/>
            <enum name="font2" value="1"/>
            <enum name="font3" value="2"/>
        </attr>
    </declare-styleable>

Дальше возвращаемся в наш класс CustomFontsTextView и пишем вот такой метод:

   public class CustomFontsTextView extends TextView {
    public CustomFontsTextView(Context context) {
        super(context);
    }

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

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

    public CustomFontsTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        setFonts(attrs,context);
    }


    private void setFonts(AttributeSet attributeSet, Context context){
        TypedArray a = context.getTheme().obtainStyledAttributes(
                attributeSet,
                R.styleable.CustomFontsTextView,
                0, 0);
        a.recycle();
        int fonts = a.getInt(R.styleable.CustomFontsTextView_fonts,-1);
        SingletonFonts singltonFonts = SingletonFonts.getInstance(context);
        switch (fonts){
            case (0):
                setTypeface(singltonFonts.getFont1());
                break;
            case (1):
                setTypeface(singltonFonts.getFont2());
                break;
            case (2):
                setTypeface(singltonFonts.getFont3());
                break;
        }
    }
}

Вот собственно и все. Теперь нужно перебилдить проект, чтобы у вас появились кастомные атрибуты. После этого отправляемся в xml файл нашей активити и пишем:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="library.draw.myapplication.MainActivity">

    <library.draw.myapplication.CustomFontsTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_margin="16dp"
        app:fonts="font1"
        android:textSize="24sp"
        android:id="@+id/text_view_1"
        android:text="Hello World!" />
    <library.draw.myapplication.CustomFontsTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_margin="16dp"
        android:textSize="48sp"
        app:fonts="font2"
        android:id="@+id/text_view_2"
        android:text="Hello World!" />
    <library.draw.myapplication.CustomFontsTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_margin="16dp"
        android:textSize="24sp"
        app:fonts="font3"
        android:id="@+id/text_view_3"
        android:text="Hello World!" />

</LinearLayout>

В привью мы увидим уже полностью готовые текстовые поля с нашими шрифтами.

image
Обращаться к нашим кастомным текстовым полям лучше как к обычному TextView, по стандартной форме:

 TextView textView = (TextView) findViewById(R.id.text_view_1);

Спасибо за внимание.
Поделиться с друзьями
-->

Комментарии (24)


  1. lazexe
    17.11.2016 11:51
    -1

    Ну уже б раз такое дело то зделали бы library, как например здесь.
    И кстати вопрос: а если мне нужен и так уже кастомный textView (чужой) то Ваш вариант не особо «катит».


    1. jawaharlalnehru
      18.11.2016 00:31

      Катит, просто наследуете свой класс от чужого кастомного textView, а не просто от TextView.


    1. palamarenco
      18.11.2016 01:01

      Нужно просто наследовать нужный вам textView и вы получите класс с теми же конструкторами в которых можно выполнить метод создания шрифтов. Так что проблем быть не должно.


  1. pfemidi
    17.11.2016 13:49
    -1

    Сначала описан как раз простой способ:

    Создать ее можно либо вручную в корне main:
    \app\src\main\assets


    А после него:

    Либо более простым способом

    [тут скриншот с кучей вложенных менюшек которые ещё попробуй найди сначала]



    Да, второй способ по сравнению с тупым созданием каталога \app\src\main\assets действительно реально проще :-) Только вот для кого? Не для обычных людей точно.


    1. expromt
      18.11.2016 00:32

      Не соглашусь :-)

      1. На скриншоте все пункты меню подписаны. Достаточно 1 раз прочесть. Запомнить легко, так как последовательность очень логична.
      2. Любой, кто работал с Андроид студией узнает все эти меню с первого взгляда.


    1. palamarenco
      18.11.2016 00:58

      Я постарался сделать скрин что-бы было максимально понятно куда нажимать, по подсветке вкладок все отлично видно. По моему мнению, второй способ реально быстрее, но вы вольны делать как вам угодно :)


  1. Dimezis
    17.11.2016 14:56
    +1

    Как по мне, лучший способ задать кастомный шрифт — сделать это в xml через DataBinding.
    https://github.com/lisawray/fontbinding

    Не надо наследоваться, нет зависимости от конкретной реализации TextView, не надо каждый раз писать boilerplate


    1. punksta
      17.11.2016 23:43

      красиво, модно, но не работает для свойств из стилей и тем.


      1. Dimezis
        18.11.2016 00:30

        Ммм… честно говоря, совсем не понял о чем вы.
        Что не работает? Установка шрифта для кастомного аттрибута?


        1. punksta
          18.11.2016 04:25

          датабиндинг работает только для атрибутов явно прописанных во вью. Вы не можете задавать шрифты в стилях или темах.


  1. anton9088
    17.11.2016 15:05

    https://github.com/johnkil/Android-RobotoTextView
    не кастомные шрифты, но для поддержки roboto на android < 4.1


  1. fRoStBiT
    17.11.2016 15:34
    +2

    А чем не угодила библиотека https://github.com/chrisjenx/Calligraphy?


    1. pihariev
      18.11.2016 00:52

      Потому что иногда так хочется написать велосипед, аж сил нет сдерживать себя :)


    1. palamarenco
      18.11.2016 00:55

      Лично я ее не использовал. Посмотрел по ссылке, не совсем понятно как данная библиотека кешит шрифты.


  1. punksta
    17.11.2016 23:47

    Это не самые важные проблемы. Для меня было неожиданностью следующее: 1) поддерживаются не все шрифты. Если шрифт не поддерживается, то будет использован стандартный. Проверить поддерживается ли шрифт нельзя. 2) нет поддержки файлов шрифтов с несколькими шрифтами внутри.


  1. Nec1ord
    18.11.2016 00:28

    Вы забыли заресайклить TypedArray:


    private void setFonts(AttributeSet attributeSet, Context context){
            TypedArray a = context.getTheme().obtainStyledAttributes(
                    attributeSet,
                    R.styleable.CustomFontsTextView,
                    0, 0);
            ...
            a.recycle();  // <- Here
        }


    1. palamarenco
      18.11.2016 00:30

      Не совсем понимаю зачем нужно ресайклить.


      1. Dimezis
        18.11.2016 01:10

        Существует пул из TypedArray.
        Если вы не сделаете ресайкл, системе ресурсов позже придется создать и добавить новый TypedArray в пул. Если заресайклить, то TypedArray можно будет переиспользовать.
        Небольшая оптимизация по памяти.


        1. palamarenco
          18.11.2016 11:16

          понял, учту


  1. aldrhabr
    18.11.2016 00:29

    во-первых наследоваться все же лучше от https://developer.android.com/reference/android/support/v7/widget/AppCompatTextView.html
    а во-вторых вот здесь есть изящненький гайд
    http://android-activities.blogspot.ru/2014/03/add-custom-behavior-to-standard-android.html


  1. AndXor
    18.11.2016 00:29

    Для создания кастомных вьюшек рекомендуется использовать классы из AppCompat. В данном случаи надо было наследоваться от AppCompatTextView.


    1. palamarenco
      18.11.2016 00:30

      Действительно, лучше использовать AppCompatTextView.Спасибо за совет :)


  1. punksta
    18.11.2016 04:39

    На сколько мне известно, существует несколько способов применения шрифта к TextView: 1) наследование 2)проход по всей иерархии вью с поиском TextView с нужным атрибутам. 3) кастомный layoutInflaiter(как в каллиграфии) 4) датабиндинг. Неплохо было бы сравнить эти способы, говоря о шрифтах.


  1. Jukobob
    21.11.2016 13:18
    +1

    Вначале скажу что я разработчик со стажем Android 6 лет.
    А теперь пару замечаний и как сделать правильнее.

    Скажу честно, я делал как вы 4 года назад.
    Ваши ошибки:
    0) Зачастую в приложении используется не только TextView, а также банально Button и EditText. Судя по логике, вы будете создавать наследников и этих классов. Но, как только вам потребуется использовать какой то расширенный класс от данных (сторонние либы), то вам придется наследоваться и от них. Это плохой путь.
    -Решение: Если вы создаете так называемые Support Class (ваши синглтончики) то разумнее создать метод TextUtils.setTypeface(view: TextView, font: String). Это решит проблему с множественным не нужным наследованием, так как Button и EditText являются наследниками TextView

    1) Запомните, что лучше не расширять базовые классы вью без крайней необходимости! Лучше найти способы более изящные
    -Решение 0: Calligraphy Lib
    -Решение 1: DataBinding + BindingAdapter
    -Решение 2: Kotlin + extension function