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

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

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

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

14 ответов

Решение

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

Цитировать документацию:

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

Акцент мой.

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

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

Ни один из предыдущих ответов не помог мне на 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.

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

Я нашел этот вопрос на Stackru наиболее полезным.

Я добавил следующий код в 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 = (float) 1.30;
        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 - "Большой" в Note 5, но, вероятно, немного отличается от устройства к устройству). Если пользователь установил слишком большой шрифт ("Extra Large", "Huge"...), мы масштабируем приложение только до "Large".

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

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

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

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

Не забудьте добавить тег манифеста 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);
    }
}

ПРЕДУПРЕЖДЕНИЕ! Это полностью игнорирует пользовательские настройки Accessibility Font Scale и предотвратит масштабирование шрифтов вашего приложения!

Вот как вы это делаете в 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, вы можете обнаружить, что некоторые части вашего приложения не масштабируются.

Существует другой способ предотвратить проблему макета приложения / проблему шрифта с помощью изменения размера шрифта. Ты можешь попробовать

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

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

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

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

Я столкнулся с той же проблемой и исправил ее, изменив на dpв файле .XML. Однако мне также нужно исправить размер текста WebViews.

Обычно для настройки размера текста WebViewв setDefaultFontSize()используется функция. Однако его единицей измерения по умолчанию является sp.

В моем проекте я использовал setTextZoom(100)чтобы исправить размер текста и значка WebView. * (Есть и другие методы в stackoverflow, но почти все они устарели)*

      WebSettings settings = mWebView.getSettings();
settings.setTextZoom(100);

Для получения дополнительной информации о setTextZoom()

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

Вы не сделаете свое приложение лучше для слабовидящих людей.

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


Реализация (10.02.22)

После дня размышлений я создал расширение Kotlin для (также может использоваться дляEditTextпотому что это ребенок):

      fun TextView.removeFontScale() {
    val fontScale = resources.configuration.fontScale
    if (fontScale != 1f) {
        val scaledTextSize = textSize
        val newTextSize = scaledTextSize / fontScale / fontScale
        textSize = newTextSize
    }
}

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


ОБНОВЛЕНИЕ/ ИСПРАВЛЕНИЕ (14.02.22)

Предыдущее решение не работает на реальных устройствах, только на эмуляторе (наоборот, это увеличивает размер текста). Итак, я нашел другой способ:

      val scaledTextSize = textSize
val newTextSize = scaledTextSize / fontScale
setTextSize(TypedValue.COMPLEX_UNIT_PX, newTextSize)

Здесь новый textSizeза TextViewпосле настройки не будет масштабироваться на fontScaleценность.


Используйте пример:

      titleText.removeFontScale()
subtitleText.removeFontScale()

Логи работы, размер в масштабе 1.3/1.5 становятся как в масштабе 1.0 :

  • Старая реализация (10.02.22):

            fontScale: 1.0 | oldSize: 20    
    fontScale: 1.3 | oldSize: 26 | newSize: 20.0
    
  • Новая реализация (14.02.22):

            fontScale: 1.5 | oldSize: 83.0 | newSize: 55.333332 | textSize: 55.333332
    fontScale: 1.5 | oldSize: 58.0 | newSize: 38.666668 | textSize: 38.666668
    

PS я замечаю, что стандарт Toolbarне масштабировал шрифты, и после этого я понимаю, что это ОК (отключить масштабирование там, где оно действительно необходимо).

Вы можете принудительно установить размер текста вашего приложения с помощью конфигурации базовой активности, сделав все действия неотъемлемой частью базовой активности. 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);
}

Назначение FontScale непосредственно контексту newBase в методе AttachBaseContext действия помогло мне.

      override fun attachBaseContext(newBase: Context?) {

    //Ignore font size changed in device settings and set the default
    newBase?.resources?.configuration?.fontScale = 1.0f

    super.attachBaseContext(newBase)
}

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

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

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

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

Я использовал этот ответ раньше, но это не обновило ориентацию экрана при вращении.

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

    super.attachBaseContext(newBase);
}

Я модифицирую некоторый код, это обновит ориентацию экрана при вращении.

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

На Android 14 или более поздней версии выполните сбросDisplayMetrics.scaledDensityне будет работать, так какfontScaleConverterбыло введено для обработки масштаба шрифта:

ПереопределитьgetResources()метод в действии, как показано ниже:

      override fun getResources(): Resources {
    val resources = super.getResources()
    resources.updateConfiguration(
        Configuration(resources.configuration).apply {
            fontScale = 1.0f
        },
        resources.displayMetrics
    )
    return resources
}

Я думаю, что ближайший лучший ответ - от пользователя ниже: «Если у вас есть работающее приложение, переключение всех значений sp на значения dp подвержено ошибкам и требует больше времени для этого. Затем, если вы решите снова переключиться на поддержку, вам нужно чтобы отменить все эти изменения. Просто имеет смысл переопределить FontScale в родительском действии. Это требует меньших усилий, чем изменение ресурсов. Это также обеспечивает лучшую ремонтопригодность. Ответ ниже, переопределяющий контекст, — это то, что вам нужно. – parohy» 7 апреля 2021 г. в 5:18

Другие вопросы по тегам