как предотвратить эффекты изменения размера системного шрифта в приложении Android?

Недавно я закончил разработку своего приложения для Android. Я использовал sp (масштабированные пиксели) для всех textSize. Проблема в том, что когда я настроил системный размер шрифта, размеры шрифта моего приложения меняются. Я могу использовать dp (независимые от устройства пиксели), но поддержка моего приложения займет слишком много времени.

Я ссылался на размер текста из этого.

Есть ли способ предотвратить изменение размера системного шрифта в моем приложении?


person james    schedule 04.02.2014    source источник


Ответы (8)


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

Процитируем документацию:

sp — это та же базовая единица, но она масштабируется в соответствии с предпочтительным размером текста пользователя (это пиксели, не зависящие от масштаба), поэтому вы должны использовать эту единицу измерения при определении размера текста (но не для размеров макета). ).

Акцент мой.

Итак, вы видите ожидаемое поведение при использовании sp в качестве единиц измерения размера текста.

Я не понимаю, что вы имеете в виду, говоря, что использование dp требует слишком много времени для поддержки вашего приложения - насколько я могу судить, это будет точно такое же количество усилий? (возможно, меньше, хотя это, вероятно, сделает его менее удобным для пользователей с плохим зрением)

person Adam S    schedule 04.02.2014
comment
Я согласился с вашим объяснением. Но в IOS это ограничивается определенными приложениями, такими как календарь, почта и т. Д. Системой. Для Android ограничений нет. Итак, могу ли я запретить моему приложению изменять размер системного шрифта. - person james; 04.02.2014
comment
Вы не можете запретить пользователю изменять размер системного шрифта; вам просто нужно использовать dp и игнорировать ошибки lint! Существует сообщение в блоге о том, как отключить ошибки lint, которые могут оказаться полезными. - person Adam S; 04.02.2014
comment
Хотя ваше использование может быть очень специфическим и требовать постоянного размера шрифта, я бы рекомендовал подумать о том, как вы можете использовать различные размеры шрифта в своих макетах, а не просто избегать этого вообще. Это значительно улучшит работу ваших пользователей. - person Adam S; 04.02.2014
comment
Если у вас есть работающее приложение, переключение всех значений sp на значения dp подвержено ошибкам и требует больше времени для этого. Затем, если вы решили снова вернуться к его поддержке, вам нужно отменить все эти изменения. Просто имеет смысл переопределить fontScale в родительском действии. Это требует меньше усилий, чем изменение ресурсов. Это также лучшая ремонтопригодность. Ответ ниже, переопределение Context - это путь. - person parohy; 07.04.2021
comment
Спасибо за этот отличный ответ. - person Wowo Ot; 25.07.2021

Недавно я тоже столкнулся с этой проблемой. Наш пользовательский интерфейс плохо масштабировался на телефонах с ограниченными размерами экрана, и изменение всего пользовательского интерфейса в случае, если пользователь установил для своих параметров доступности значение «Огромный», казалось глупым.

Я обнаружил, что этот вопрос на StackOverflow самый полезный.

Что я сделал, так это поместил следующий код ниже в свой BaseActivity (класс Activity, из которого распространяются все мои действия)

public void adjustFontScale(Configuration configuration) {
    if (configuration.fontScale > 1.30) {
        LogUtil.log(LogUtil.WARN, TAG, "fontScale=" + configuration.fontScale); //Custom Log class, you can use Log.w
        LogUtil.log(LogUtil.WARN, TAG, "font too big. scale down..."); //Custom Log class, you can use Log.w
        configuration.fontScale = 1.30f;
        DisplayMetrics metrics = getResources().getDisplayMetrics();
        WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
        wm.getDefaultDisplay().getMetrics(metrics);
        metrics.scaledDensity = configuration.fontScale * metrics.density;
        getBaseContext().getResources().updateConfiguration(configuration, metrics);
    }
}

И назвал это сразу после моего super.onCreate() вот так

adjustFontScale(getResources().getConfiguration());

Этот код определяет, установил ли пользователь масштаб шрифта в настройках специальных возможностей на нечто большее, чем 1.30f (1.30f — это Large на Note 5, но, вероятно, немного отличается от устройства к устройству). Если пользователь установил слишком большой шрифт (Extra Large, Huge...), мы масштабируем приложение только до размера Large.

Это позволяет вашему приложению масштабироваться в соответствии с предпочтениями пользователя (до определенной степени), не искажая ваш пользовательский интерфейс. Надеюсь, это поможет другим. Удачи в масштабировании!

Другие советы

Если вы хотите, чтобы определенные макеты масштабировались с вашими шрифтами (скажем... RelativeLayout, который вы используете в качестве фона для ваших шрифтов), вы можете установить их ширину/высоту с помощью sp вместо классического dp. Когда пользователь изменяет размер своего шрифта, макет будет меняться соответственно шрифтам в вашем приложении. Хороший маленький трюк.

person welshk91    schedule 28.09.2015
comment
Могу я спросить, почему ваш пример немного отличается от вопроса, который вы связали, относительно этой строки: WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);. Я новичок в Android и пытаюсь выбрать «лучший» ответ. Спасибо! - person Tallboy; 13.03.2016
comment
@Tallboy хороший вопрос. Я видел, как код получает оконный менеджер в обоих направлениях (используя вызов getSystemService, как я, и используя getWindowManager(), как в примере, на который я ссылался). Я только что проверил свой код в обоих направлениях, и они оба работали нормально. Кажется, я где-то читал, что getWindowManager возвращает объект оконного менеджера, доступный только для чтения, и это может быть ключевой разницей между двумя методами, но для этого примера это не имело значения. - person welshk91; 15.03.2016
comment
Это не работает на Нуге. До Marshmallow работает нормально. - person Nayan; 29.03.2017
comment
Я несколько дней просматривал всю сеть, и это единственное решение, которое работает с конденсатором! Спасибо! - person nachshon f; 20.10.2020
comment
Метод updateConfiguration устарел! - person Suneesh Ambatt; 01.07.2021

Ни один из предыдущих ответов не работал у меня на Android 8.1 (API 27). Вот что сработало: добавьте следующий код в свою активность:

Код Котлина:

override fun attachBaseContext(newBase: Context?) {
    super.attachBaseContext(newBase)
    val newOverride = Configuration(newBase?.resources?.configuration)
    newOverride.fontScale = 1.0f
    applyOverrideConfiguration(newOverride)
}

Java-код:

@Override
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(newBase);
    final Configuration override = new Configuration(newBase.getResources().getConfiguration());
    override.fontScale = 1.0f;
    applyOverrideConfiguration(override);
}

Вам не нужно менять AndroidManifest.xml.

person diogenesgg    schedule 26.07.2019
comment
Это решение для Android API › N, не тестировалось на устройствах ниже N. - person Dr. DS; 18.04.2020
comment
java.lang.IllegalStateException: getResources() или getAssets() уже вызывались - person Hiren Patel; 20.11.2020
comment
@Dr.DS это API › N или API ›= N ? Для меня это не работает на N. Просто хотел подтвердить - person Sethuraman Srinivasan; 30.11.2020
comment
@Dr.DS Dr.DS Я протестировал устройство ниже N, которое работает хорошо. - person Nik; 21.01.2021
comment
@diogenesgg работает нормально, но активность воссоздается, когда я меняю уровень шрифта / масштабирования, когда приложение находится в фоновом режиме, поэтому я потерял данные в диалоговом окне и других вычислениях, я также не могу вызвать метод onconfigurationchanged() из базовой активности. - person Naseer Attari; 25.01.2021

Вот как мы это делаем. В классе Application переопределите onConfigurationChanged() следующим образом. Если вам нужно другое поведение для разных действий, переопределите onConfigurationChanged() в действии.

Не забудьте добавить тег манифеста android:configChanges="fontScale", так как вы сами управляете этим изменением конфигурации.

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // In some cases modifying newConfig leads to unexpected behavior,
    // so it's better to edit new instance.
    Configuration configuration = new Configuration(newConfig);
    SystemUtils.adjustFontScale(getApplicationContext(), configuration);
}

В некотором вспомогательном классе у нас есть метод adjustFontScale().

public static void adjustFontScale(Context context, Configuration configuration) {
    if (configuration.fontScale != 1) {
        configuration.fontScale = 1;
        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        wm.getDefaultDisplay().getMetrics(metrics);
        metrics.scaledDensity = configuration.fontScale * metrics.density;
        context.getResources().updateConfiguration(configuration, metrics);
    }
}

ВНИМАНИЕ! Это полностью игнорирует пользовательские настройки масштабирования шрифта специальных возможностей и предотвратит масштабирование шрифтов вашего приложения!

person localhost    schedule 06.09.2016
comment
Это не работает. onConfigurationChanged никогда не запускается. - person zed; 10.01.2017
comment
@zed вам нужно установить android:configChanges=orientation|screenSize в приложении или активности в манифесте - person speedynomads; 06.12.2017
comment
Это решение работает только на планшетах, а не на смарт-устройствах. - person Praveen Kumar Verma; 11.01.2019
comment
это работает на Nexus 5-Android 6, я проверю на другом устройстве. - person Sameer Z.; 14.09.2019
comment
Не работает на Android 9 - person Krutika Chotara; 08.05.2021
comment
Глядя на несколько реализаций исходного кода (в 5.1 и 10) для updateConfiguration, обновление scaledDensity вручную не требуется. Они делают это для вас. - person Peter; 20.05.2021

Вот как вы это делаете в 2018 году (Xamarin.Android/C# — такой же подход на других языках):

public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
    protected override void OnCreate(Bundle bundle)
    {
...
    }

    protected override void AttachBaseContext(Context @base)
    {
        var configuration = new Configuration(@base.Resources.Configuration);

        configuration.FontScale = 1f;
        var config =  Application.Context.CreateConfigurationContext(configuration);

        base.AttachBaseContext(config);
    }
}

Все, что вам нужно, это переопределить метод активности attachBaseContext и обновить там конфигурацию.

getBaseContext().getResources().updateConfiguration() устарел, хотя существует множество примеров использования этого метода. Если вы используете этот подход помимо предупреждения IDE, вы можете обнаружить, что некоторые части вашего приложения не масштабируются.

person Maxim Saplin    schedule 29.12.2018

Есть еще один способ предотвратить проблему макета приложения / проблемы со шрифтом из-за изменения размера шрифта. Можешь попробовать

// ignore the font scale here
final Configuration newConfiguration = new Configuration(
    newBase.getResources().getConfiguration()
);

newConfiguration.fontScale = 1.0f;
applyOverrideConfiguration(newConfiguration);

где newBase — из функции attachBaseContext. Вам нужно переопределить этот обратный вызов в вашей деятельности.

Но побочный эффект заключается в том, что если вы хотите использовать анимацию (objectanimator/valueanimator), это вызовет странное поведение.

person Chauyan    schedule 24.03.2018

вы можете принудительно изменить размер текста вашего приложения, используя конфигурацию базовой активности, сделать все действия неотъемлемой базовой активностью. 1.0f установит нормальный размер шрифта приложения, игнорируя системные настройки.

public  void adjustFontScale( Configuration configuration,float scale) {

configuration.fontScale = scale;
DisplayMetrics metrics = getResources().getDisplayMetrics();
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(metrics);
metrics.scaledDensity = configuration.fontScale * metrics.density;
getBaseContext().getResources().updateConfiguration(configuration, metrics);

}

@Override
 protected void onCreate(@Nullable Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     adjustFontScale( getResources().getConfiguration(),1.0f);
}
person Usama Saeed US    schedule 08.05.2020

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

fun TextView.getSizeInSp() = textSize / context.resources.displayMetrics.scaleDensity

fun TextView.convertToDpSize() = setTextSize(TypedValue.COMPLEX_UNIT_DIP, getSizeInSp())

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

person fung    schedule 23.11.2020