Динамическое изменение размера шрифта во всем приложении на Android с помощью Configuration.fontScale

Доброго времени суток, уважаемые читатели.

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

Изменение размера шрифта решил делать во всем приложении. Но использовать метод setTextSize в каждом activity не вариант, т.к. в один прекрасный момент появится новое поле и придется на нем вновь прописывать нужный размер. Поэтому, решением было сделать автоматическое изменение во всех местах.

Когда писал данную статью, суть заключалась в следующем: в настройках приложения хранится коэффициент увеличения шрифта. Этот коэффициент применяется на реальный размер шрифта в собственном классе, наследованным от TextView. Но Ganster41 подсказал более хорошее решение. Поэтому сперва будет описание первоначального решение, а в конце будет реализация с помощью Configuration.fontScale.

Первоначальная реализация


Начнем с того, что в нужном месте приложения добавим функционал выбора и сохранения коэффициента:
Код
float new_coef = 1.3f;
//активируем параметры
SharedPreferences settings = getSharedPreferences("MyAppSett", MODE_PRIVATE); 
Editor value_add = settings.edit();
//заносим новый коэффициент увеличения шрифта
value_add.putFloat("size_coef", new_coef); 
value_add.commit();


Естественно new_coef нужно заполнять динамически. Например при выборе значения на бегунке.

Далее этот коэффициент необходимо считать в нужном месте и с помощью него изменять размер шрифта. Каждый раз считывать его из настроек приложения, при активации того или иного TextView, не совсем хорошее решение. Поэтому коэффициент будем считывать один раз при активации приложения и хранить в глобальной переменной. Для этого добавим новый класс приложения:
Код
package com.Example.Test;

import android.app.Application;
import android.content.SharedPreferences;

public final class MyApp extends Application {
  private float size_coef; //коэффициент увеличения шрифта

  //получить коэффициент размера шрифта
  public float getSizeCoef() {
    return size_coef;
  }

  //инициализация приложения
  @Override
  public void onCreate() {
    super.onCreate();
    //считываем коэффициент увеличения шрифта
    SharedPreferences settings = getSharedPreferences("MyAppSett", MODE_PRIVATE);
    size_coef= settings.getFloat("size_coef", 1f);
  }
}


Далее в файле манифеста укажем новый наш класс:
Код
<application
  android:name=".MyApp"
  android:icon="@drawable/ic_launcher"
  android:label="@string/app_name"
  android:theme="@style/AppTheme" >


После этого необходимо создать еще один класс, но уже на основе TextView:
Код
package com.Example.Test;

import android.app.Activity;
import android.content.Context;
import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;
import android.util.TypedValue;

//класс приложения с глобальной переменной
public final class MyTextView extends AppCompatTextView {

  //инициализация касса
  public MyTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
    InitSize(context);
  }

  //установка нужного размера
  private void InitSize(Context context) {
    //получаем коэффициент увеличения шрифта
    Activity activity = (Activity) context;
    float koef = ((MyApp) activity.getApplication()).getSizeCoef();

    float cur_size = getTextSize(); //текущий размер шрифта в пикселях
    //устанавливаем новый размер шрифта в пикселях
    setTextSize(TypedValue.COMPLEX_UNIT_PX, cur_size * koef);
  }
}


Обратите внимание на то, что у метода setTextSize первый параметр по умолчанию = TypedValue.COMPLEX_UNIT_SP. Что означает установку размера шрифта в sp единицах измерения. В нашем же случае используется TypedValue.COMPLEX_UNIT_PX. Этот тип необходимо указывать, чтобы задать размер шрифта в пикселях, т.к. getTextSize возвращает текущий размер в пикселях.

В принципе все подготовительные классы готовы. Осталось в нужном месте разметки вместо TextView указать свой собственный класс MyTextView:
Код
<com.Example.Test.MyTextView
  android:id="@+id/TextValue"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:textColor="@color/TextColor"
  android:textSize="@dimen/TextSize" />


В итоге при открытии activity у данного текста будет изменен размер шрифта на тот, что выбрал пользователь. С EditText все делается аналогично.

Обновленная реализация


Для себя я решил использовать коэффициент размера шрифта от 0.7f до 1.45f с интервалом 0.15f. Т.е. это 6 шагов. Для выбора конкретного значения использую SeekBar.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <SeekBar
    android:id="@+id/seekBar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:progress="0"
    android:max="5" />
</LinearLayout>

В нужном месте приложения (в методе onCreate) реализуем обработку выбранного значения на SeekBar:
final float start_value = 0.7f; //начальное значение размера шрифта
final float step = 0.15f; //шаг увеличения коэффициента
int size_coef; //выбранный коэффициент
...
SeekBar seekBar = (SeekBar) findViewById(R.id.seekBar);
...
if (seekBar != null) {
  seekBar.setProgress(select_coef);
  seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
      size_coef = progress; //выбранный коэффициент
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
    }
  });
}

Обработку выбора значения на бегунке сделали. Теперь необходимо сохранить выбранный результат и сразу же изменить размер шрифта (например по кнопке применять):
//активируем параметры
SharedPreferences settings = getSharedPreferences("MyAppSett", MODE_PRIVATE);
SharedPreferences.Editor value_add = settings.edit();
//заносим новый коэффициент увеличения шрифта
value_add.putInt("size_coef", size_coef); 
value_add.apply();

//устанавливаем размер шрифта в приложении
Resources res = getResources();
float  сoef = start_value + size_coef * step; //новый коэффициент увеличения шрифта
Configuration configuration = new Configuration(res.getConfiguration());
configuration.fontScale = сoef;
res.updateConfiguration(configuration, res.getDisplayMetrics());

Теперь при смене activity будет новый размер шрифта во всех местах приложения. Но на данный момент этот размер будет только до перезагрузки приложения. При открытии приложения произойдет установка размера шрифта того, что установлен в настройках android на устройстве. И соответственно, чтобы в приложении был нужным нам размер, необходимо его переназначить. Для этого мы и сохраняем коэффициент в параметры приложения:
package com.Example.Test;

import android.app.Application;
import android.content.SharedPreferences;

public final class MyApp extends Application {
  
  //инициализация приложения
  @Override
  public void onCreate() {
    super.onCreate();    
    int size_coef;
    final float start_value = 0.7f; //начальное значение размера шрифта
    final float step = 0.15f; //шаг увеличения коэффициента
    Resources res = getResources();
    SharedPreferences settings = getSharedPreferences("MyAppSett", MODE_PRIVATE);
    try {
      //считываем коэффициент размера шрифта
      size_coef = settings.getInt("size_coef", 2);
    }
    catch (Exception e) {
      size_coef = 2; //коэффициент по умолчанию
    }

    //новый коэффициент увеличения шрифта
    float new_value = start_value + size_coef * step;
    //устанавливаем размер шрифта в приложении
    Configuration configuration = new Configuration(res.getConfiguration());
    configuration.fontScale = new_value;
    res.updateConfiguration(configuration, res.getDisplayMetrics());
  }
}

По умолчанию используется значение 2, т.е. в моей формуле это коэффициент увеличения шрифта равный 1 (0,7 + 0,15 * 2 = 1). Данный класс необходимо прописать в манифесте:
<application
  android:name=".MyApp"
  android:icon="@drawable/ic_launcher"
  android:label="@string/app_name"
  android:theme="@style/AppTheme" >

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

Всем спасибо за внимание.
Поделиться публикацией
Ой, у вас баннер убежал!

Ну. И что?
Реклама
Комментарии 22
  • +2
    А чем не угодило изменение поля Configuration.fontScale для всего приложения, без использования вороха кастомных вьюх?
    • 0
      Configuration.fontScale зависит от настроек всего телефона. Если в них пользователь изменил размер шрифта, то тогда и в приложении изменится размер. Такой способ мне не подошёл, т.к. не всегда пользователь меняет размер во всем телефоне, а хочет его изменить только в конкретном приложении.
      • +1
        Так меняйте конфигурацию ресурсов для своего приложения. Примеров в всети куча.
        • 0
          Если вам не сложно, то не могли бы вы скинуть ссылку на пример? Я пока ничего подходящего не смог найти. И тем более ресурсы же нельзя менять, они встраиваются в приложение в момент компиляции (это на сколько я знаю)
          • 0
            Я, например, таким образом локаль в приложении менял:
            Resources resources = getApplicationContext().getResources();
            Configuration configuration = resources.getConfiguration();
            configuration.locale = new Locale(lang);
            resources.updateConfiguration(configuration, resources.getDisplayMetrics());

            Хотя сейчас погуглил, оказывается если targetApiLevel > 17, то могут быть разные вариации. Можно почитать тут.
            • 0
              Пожалуй вы правы, таким образом можно обойтись без кастомных View и application. Большое спасибо вам за подсказку.
    • 0
      По-моему так себе способ. Это же нужно каждую вьюху переопределять. Самый простой способ, который приходит в голову это переопределить фактори для инфлейтера и реализовать там всю логику, тогда шрифт будет везде изменяться и никаких кастомных вьюх не нужно.
      • 0
        Спасибо за идею, попробую с таким способом сделаю
        • 0
          Еще можно попробовать через темы сделать, но для этого, наверное, для каждого размера шрифта в теме атрибут нужно будет создать.
          • 0
            Вариант с темами я сразу исключил для себя, т.к. не хотелось делать несколько тем и потом их связывать с конкретной величиной размера шрифта. Хотелось сделать именно функционал, чтобы пользователь могу выбрать размер не из выпадающего списка, грубо говоря, а двигая ползунок.
        • 0
          Использовали на проекте эту либу github.com/chrisjenx/Calligraphy

          Если не ошибаюсь, в ней так и сделано.
        • 0
          Спасибо за идею, попробую с таким способом сделаю
          • 0
            • От слова «Динамическое» в этом решении нет абсолютно ничего. После инициализации приложения происходит единственный вызов onCreate в Application и вы инициализируете константу. То есть для применения размера шрифта вы храните постоянную переменную. Обновление SharedPreferences в Realtime не приведет ни к каким «Динамическим» изменениям
            • Шрифты используются во всех наследниках TextView. Решать данную задачу через наследование — есть крайне грубая ошибка. Вы будите вынуждены писать аналогичные классы ко всем наследникам TextView (Button, Checkbox and etc)
            • 0
              Согласен с Вами, что писать свой класс под каждый наследник — глупо. Почитав комментарии, решил переделать свою реализацию на использование Configuration.fontScale, как подсказал Ganster41. По необходимости могу показать пример реализации.
              • 0
                Да, это решение более оптимально, чем предыдущее. Но отсутсвует «Динамическое», так как вам необходимо пересоздавать View для применения конфигурации. к
                • 0
                  Было бы интересно посмотреть такую реализацию, начал делать сперва по примеру, но споткнулся на создании класса для MySpinner +) Там не срабатывает установка размеров такая
                  • 0
                    Хорошо, подготовлю пример реализации
                    • 0
                      Обновил статью, добавил в конце новую реализацию.
                      • 0
                        спасибо дорогой друг =) завтра буду пробовать присобачить, сегодня пол дня провозился с подстройкой под твою первую реализацию, и даже почти все кроме спинера под нее перевел, погляжу как вторая будет работать
                        • 0
                          Вторая реализация нормально меняет размер у спинера. Только что проверил в своём приложении.
                          • 0
                            Все сработало, спасибо, с изменением размеров шрифтов бился уже давно, и нормального мануала на эту тему не получалось найти, большой тебе респект!
                            P.S. не знаю уж почему, у меня res. не сработало, заменил на getResources().
                            • 0
                              Моя невнимательность. Забыл добавить объявление переменной: Resources res = getResources().
                              Подправил описание.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое