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

Я разрабатываю приложение с React Native как для iOS, так и для Android, и я пытаюсь предотвратить масштабирование дисплея в приложении для конкретного устройства.

Для масштабирования размера текста/шрифта размещение следующего кода в корневом файле App.js решает проблему как для iOS, так и для Android:

if (Text.defaultProps == null) {
    Text.defaultProps = {};
}
Text.defaultProps.allowFontScaling = false;

Однако на устройствах Android есть следующий параметр размера экрана, который все еще применяется:

Настройка размера дисплея устройства Android

Я пытался (безуспешно) собрать воедино различные «решения» этой проблемы, которые я нашел в ответах на следующие вопросы:

Изменить размер системного дисплея программно Android N

Отключение масштабирования приложения или активности, если установлено -› Дисплей -› Размер экрана изменен на Большой или маленький

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

Я часто находил ссылки на класс BaseActivity, который расширяет класс Activity. Насколько я понимаю, это внутри этого класса, где я буду писать метод (назовем его adjustDisplayScale), чтобы внести изменения в Configuration из Context, которые я получаю от Resources, и что затем я буду вызывать adjustDisplayScale в методе onCreate() после super.onCreate() в файле MainApplication.java.

На данный момент в этом каталоге у меня всего два файла — MainApplication.java и MainActivity.java.

Я попытался создать новый модуль и связанный файл пакета для реализации adjustDisplayScale, следуя этим инструкциям, и это не сработало: https://facebook.github.io/react-native/docs/text.html

Я попытался реализовать функциональность adjustDisplayScale в onCreate() вот так, и это не сработало:

@Override
public void onCreate() {
    super.onCreate();

    Context context = getApplicationContext();
    Resources res = context.getResources();
    Configuration configuration = res.getConfiguration();

    configuration.fontScale = 1f;
    DisplayMetrics metrics = res.getDisplayMetrics();

    WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
    wm.getDefaultDisplay().getMetrics(metrics);
    metrics.scaledDensity = 1f;
    configuration.densityDpi = (int) res.getDisplayMetrics().xdpi;
    context = context.createConfigurationContext(configuration);

    SoLoader.init(this, /* native exopackage */ false);
}

Потенциально многообещающий ответ включал следующее:

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);
}

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

Немного предыстории... Я выполнил 99% своей работы над этим проектом на JavaScript/React Native, и у меня почти нет понимания о таких вещах, как Resources, Context, Configuration и DisplayMetrics, связанных с разработкой Android, И последний раз, когда я писал код на Яве было 10 лет назад. Я провел несколько мучительных часов, пытаясь понять это, и любая помощь будет большой признательна.

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




Ответы (2)


ОБНОВЛЕНИЕ

Мой первый ответ не работает, если изменить разрешение экрана. На устройствах Samsung вы можете изменить масштаб экрана, но вы также можете изменить разрешение экрана на некоторых моделях (Настройки->Дисплей->Разрешение экрана->HD, FHD, WQHD и т. д.).

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

Одно дополнительное замечание. В идеале вам не нужно использовать такой код, чтобы обойти масштабирование экрана. В определенном смысле масштабирование экрана просто имитирует большие или меньшие экраны. Таким образом, если ваше приложение правильно поддерживает различные размеры экрана, вам не нужно полностью отключать масштабирование экрана.

public class BaseActivity extends AppCompatActivity {

    @TargetApi(Build.VERSION_CODES.N)
    private static final int[] ORDERED_DENSITY_DP_N = {
            DisplayMetrics.DENSITY_LOW,
            DisplayMetrics.DENSITY_MEDIUM,
            DisplayMetrics.DENSITY_TV,
            DisplayMetrics.DENSITY_HIGH,
            DisplayMetrics.DENSITY_280,
            DisplayMetrics.DENSITY_XHIGH,
            DisplayMetrics.DENSITY_360,
            DisplayMetrics.DENSITY_400,
            DisplayMetrics.DENSITY_420,
            DisplayMetrics.DENSITY_XXHIGH,
            DisplayMetrics.DENSITY_560,
            DisplayMetrics.DENSITY_XXXHIGH
    };

    @TargetApi(Build.VERSION_CODES.N_MR1)
    private static final int[] ORDERED_DENSITY_DP_N_MR1 = {
            DisplayMetrics.DENSITY_LOW,
            DisplayMetrics.DENSITY_MEDIUM,
            DisplayMetrics.DENSITY_TV,
            DisplayMetrics.DENSITY_HIGH,
            DisplayMetrics.DENSITY_260,
            DisplayMetrics.DENSITY_280,
            DisplayMetrics.DENSITY_XHIGH,
            DisplayMetrics.DENSITY_340,
            DisplayMetrics.DENSITY_360,
            DisplayMetrics.DENSITY_400,
            DisplayMetrics.DENSITY_420,
            DisplayMetrics.DENSITY_XXHIGH,
            DisplayMetrics.DENSITY_560,
            DisplayMetrics.DENSITY_XXXHIGH
    };

    @TargetApi(Build.VERSION_CODES.P)
    private static final int[] ORDERED_DENSITY_DP_P = {
            DisplayMetrics.DENSITY_LOW,
            DisplayMetrics.DENSITY_MEDIUM,
            DisplayMetrics.DENSITY_TV,
            DisplayMetrics.DENSITY_HIGH,
            DisplayMetrics.DENSITY_260,
            DisplayMetrics.DENSITY_280,
            DisplayMetrics.DENSITY_XHIGH,
            DisplayMetrics.DENSITY_340,
            DisplayMetrics.DENSITY_360,
            DisplayMetrics.DENSITY_400,
            DisplayMetrics.DENSITY_420,
            DisplayMetrics.DENSITY_440,
            DisplayMetrics.DENSITY_XXHIGH,
            DisplayMetrics.DENSITY_560,
            DisplayMetrics.DENSITY_XXXHIGH
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.v("TESTS", "Dimension: " + getResources().getDimension(R.dimen.test_dimension));
    }

    @Override
    protected void attachBaseContext(final Context baseContext) {

        Context newContext = baseContext;

        // Screen zoom is supported from API 24+
        if(Build.VERSION.SDK_INT >= VERSION_CODES.N) {

            Resources resources = baseContext.getResources();
            DisplayMetrics displayMetrics = resources.getDisplayMetrics();
            Configuration configuration = resources.getConfiguration();

            Log.v("TESTS", "attachBaseContext: currentDensityDp: " + configuration.densityDpi
                    + " widthPixels: " + displayMetrics.widthPixels + " deviceDefault: " + DisplayMetrics.DENSITY_DEVICE_STABLE);

            if (displayMetrics.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE) {
                // display_size_forced exists for Samsung Devices that allow user to change screen resolution
                // (screen resolution != screen zoom.. HD, FHD, WQDH etc)
                // This check can be omitted.. It seems this code works even if the device supports screen zoom only
                if(Settings.Global.getString(baseContext.getContentResolver(), "display_size_forced") != null) {
                    Log.v("TESTS", "attachBaseContext: This device supports screen resolution changes");

                    // density is densityDp / 160
                    float defaultDensity = (DisplayMetrics.DENSITY_DEVICE_STABLE / (float) DisplayMetrics.DENSITY_DEFAULT);
                    float defaultScreenWidthDp = displayMetrics.widthPixels / defaultDensity;
                    Log.v("TESTS", "attachBaseContext: defaultDensity: " + defaultDensity + " defaultScreenWidthDp: " + defaultScreenWidthDp);
                    configuration.densityDpi = findDensityDpCanFitScreen((int) defaultScreenWidthDp);
                } else {
                    // If the device does not allow the user to change the screen resolution, we can
                    // just set the default density
                    configuration.densityDpi = DisplayMetrics.DENSITY_DEVICE_STABLE;
                }
                Log.v("TESTS", "attachBaseContext: result: " + configuration.densityDpi);
                newContext = baseContext.createConfigurationContext(configuration);
            }
        }
        super.attachBaseContext(newContext);
    }

    @TargetApi(Build.VERSION_CODES.N)
    private static int findDensityDpCanFitScreen(final int densityDp) {
        int[] orderedDensityDp;

        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            orderedDensityDp = ORDERED_DENSITY_DP_P;
        } else if(Build.VERSION.SDK_INT >= VERSION_CODES.N_MR1) {
            orderedDensityDp = ORDERED_DENSITY_DP_N_MR1;
        } else {
            orderedDensityDp = ORDERED_DENSITY_DP_N;
        }

        int index = 0;
        while (densityDp >= orderedDensityDp[index]) {
            index++;
        }
        return orderedDensityDp[index];
    }
}

ИСХОДНЫЙ ОТВЕТ

Вы можете попробовать следующий код (переопределяющий attachBaseContext). Это отключит масштабирование экрана в вашем приложении. Это способ изменить масштаб всего экрана сразу.

@Override
protected void attachBaseContext(final Context baseContext) {

    Context newContext;

    if(Build.VERSION.SDK_INT >= VERSION_CODES.N) {

        DisplayMetrics displayMetrics = baseContext.getResources().getDisplayMetrics();
        Configuration configuration = baseContext.getResources().getConfiguration();

        if (displayMetrics.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE) {
            // Current density is different from Default Density. Override it
            configuration.densityDpi = DisplayMetrics.DENSITY_DEVICE_STABLE;
            newContext = baseContext.createConfigurationContext(configuration);
        } else {
            // Same density. Just use same context
            newContext = baseContext;
        }
    } else {
        // Old API. Screen zoom not supported
        newContext = baseContext;
    }
    super.attachBaseContext(newContext);
}

В этом коде я проверяю, отличается ли плотность тока от плотности устройства по умолчанию. Если они разные, я создаю новый контекст, используя плотность по умолчанию (а не текущую). Затем я прикрепляю этот измененный контекст.

Вы должны делать это каждый раз Activity. Итак, вы можете создать BaseActivity и добавить туда этот код. Затем вам просто нужно обновить свои действия, чтобы расширить BaseActivity

public class BaseActivity extends AppCompatActivity {
    @Override
    protected void attachBaseContext(final Context baseContext) {
        ....
    }
}

Затем в своей деятельности:

public class MainActivity extends BaseActivity {
    // Since I'm extending BaseActivity, I don't need to add the code
    // on attachBaseContext again
    // If you don't want to create a base activity, you must copy/paste that
    // attachBaseContext code into all activities
}

Я тестировал этот код с помощью:

Log.v("Test", "Dimension: " + getResources().getDimension(R.dimen.test_dimension));

Различное масштабирование экрана (используя этот код):

2019-06-26 16:38:17.193 16312-16312/com.test.testapplication V/Test: Dimension: 105.0
2019-06-26 16:38:35.545 16312-16312/com.test.testapplication V/Test: Dimension: 105.0
2019-06-26 16:38:43.021 16579-16579/com.test.testapplication V/Test: Dimension: 105.0

Различное масштабирование экрана (без этого кода):

2019-06-26 16:42:53.807 17090-17090/com.test.testapplication V/Test: Dimension: 135.0
2019-06-26 16:43:19.381 17090-17090/com.test.testapplication V/Test: Dimension: 120.0
2019-06-26 16:44:00.125 17090-17090/com.test.testapplication V/Test: Dimension: 105.0

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

Изменить

person W0rmH0le    schedule 26.06.2019
comment
Большое спасибо! Однако я не очень понимаю следующее: вы должны делать это каждое действие. Итак, вы можете создать BaseActivity и добавить туда этот код. Затем вам просто нужно обновить свои действия, чтобы расширить BaseActivity. Как правильно создать BaseActivity и использовать его в MainApplication и/или MainActivity? Мне просто создать новый файл с именем BaseActivity и скопировать форматирование MainActivity, а затем просто добавить этот метод внутри определения класса? - person Emilio; 26.06.2019
comment
Если вы не хотите создавать базовое действие, вы должны скопировать/вставить этот код attachBaseContext во все действия - когда вы говорите все действия... Я могу быть совершенно невежественным, но у меня сейчас есть только одно действие - MainActivity - это вероятно/возможно? - person Emilio; 26.06.2019
comment
И для этой части открытый класс MainActivity расширяет BaseActivity — в настоящее время мой MainActivity расширяет ReactActivity. Ожидаете ли вы заменить это на BaseActivity, вызывающее проблемы? Понадобится ли мне, чтобы BaseActivity расширял ReactActivity вместо AppCompatActivity для учета этого? - person Emilio; 26.06.2019
comment
Я привел пример для обычного приложения для Android. Думаю, вы можете заменить AppCompatActivity, как хотите. - person W0rmH0le; 26.06.2019
comment
Приходилось ли вам вносить какие-либо изменения в файл AndroidManifest.xml? - person Emilio; 27.06.2019
comment
Кроме того, нужно ли вызывать этот метод attachBaseContext где-то в файле MainApplication.java, чтобы он был выполнен? - person Emilio; 27.06.2019
comment
baseContext.createConfigurationContext (конфигурация); должен быть включен, иначе никаких изменений. Кроме того, displayMetrics совершенно не имеет значения, поскольку вы можете только проверить и установить элемент конфигурации, - person George; 06.02.2020
comment
В большинстве случаев это будет работать нормально, но на устройствах Samsung, в которых мы можем изменить разрешение на HD+, FHD+ или WQHD+, интерфейс будет искажаться. В FHD+ все работает нормально, но в HD+ экран увеличивается, а в WQHD+ уменьшается. Вы имеете какое-нибудь представление об этом? - person Gopal Awasthi; 08.02.2020
comment
@George даже после включения baseContext.createConfigurationContext(configuration); без изменений. - person Girish; 30.04.2020
comment
У меня это сработало после изменения на updateConfiguration(configuration, displayMetrics); но он работает выше Android8.0. - person Girish; 30.04.2020
comment
Могу подтвердить, это сработало, чтобы исправить некоторые странные проблемы, которые мы наблюдали, когда люди меняли размер экрана (или масштабирование экрана в версиях ОС Android с вирусами). Я сомневаюсь, что это исправит сумасшедшие изменения разрешения, которые предлагает Samsung, но это только начало! - person DiscDev; 25.08.2020
comment
@DiscDev Я обновил свой ответ, чтобы поддерживать разрешение экрана. - person W0rmH0le; 26.08.2020
comment
@GopalAwasthi обновил мой ответ для поддержки разрешения экрана.. - person W0rmH0le; 26.08.2020
comment
@ W0rmH0le, спасибо! Я попробую. Обратите внимание, что наше приложение отлично поддерживает размеры экрана. Проблема здесь в том, что у нас есть способ создать jpg, который идеально отформатирован для публикации в Instagram. Для этого мы рисуем вид за пределами экрана, а затем визуализируем его в растровое изображение. Мы хотим, чтобы содержимое этого изображения было одинакового размера, независимо от устройства, на котором оно создано. Размер дисплея, масштабирование экрана и разрешение устройства влияют на результаты. Наше приложение прекрасно работает, чтобы настроить его содержимое в соответствии с различными настройками, но в этом случае нам нужно отключить вещи, которые портят растровое изображение. - person DiscDev; 26.08.2020
comment
@ W0rmH0le Насколько я понимаю, baseContext — это синглтон. Почему мы должны копировать attachBaseContext во все действия? - person Ali_Habeeb; 27.12.2020

мы можем использовать код раздела

Configuration configuration = getResources().getConfiguration();
configuration.fontScale = (float) 1; //0.85 small size, 1 normal size, 1,15 big etc
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
configuration.densityDpi = (int) getResources().getDisplayMetrics().xdpi;
getBaseContext().getResources().updateConfiguration(configuration, metrics);

если вы нашли это полезным, вы можете проголосовать THIS_ANSWER.

person Tushar Pandey    schedule 23.12.2020