Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I prevent Android device Display size scaling in my app?

I'm developing an app with React Native for both iOS and Android, and I am trying to prevent device-specific scaling of the display in the app.

For text/font size scaling, putting the following code in the root-level App.js file solves the issue for both iOS and Android:

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

However, Android devices have the following Display size setting that is still being applied:

Android Device Display size setting

I've tried (unsuccessfully) to piece together a variety of "solutions" to this issue that I've found in answers to the following questions:

Change the system display size programatically Android N

Disabling an app or activity zoom if Setting -> Display -> Display size changed to Large or small

how to prevent system font-size changing effects to android application?

I've often found references to a BaseActivity class that extends the Activity class. My understanding is that it is inside of that class where I would be writing a method (let's call it adjustDisplayScale) to make changes to the Configuration of the Context that I get from Resources, and that then I would be calling adjustDisplayScale within the onCreate() method after super.onCreate() in the MainApplication.java file.

As of now, in this directory I just have two files - MainApplication.java and MainActivity.java.

I've attempted creating a new Module and associated Package file to implement adjustDisplayScale following these instructions and it did not work: https://facebook.github.io/react-native/docs/text.html

I've attempted placing implementing the functionality of adjustDisplayScale within the onCreate() like this and it did not work:

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

A potentially promising answer included the following:

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

But when I tried to utilize this, I got errors about not recognizing the symbol @base.

Some background... I've done 99% of my work on this project in JavaScript / React Native and I have almost no understanding about things like Resources, Context, Configuration, and DisplayMetrics associated with Android development AND the last time I wrote code in Java was 10 years ago. I've spent a number of agonizing hours trying to figure this out and any help would be greatly appreciated.

ps. I am well-aware that accessibility settings exist for a good reason so please spare me the diatribe I've seen in so many "answers" on why I need to fix my UI to work with accessibility settings rather than disable them.

like image 647
Emilio Avatar asked Jun 26 '19 18:06

Emilio


People also ask

How do I make my Android apps fit all screen sizes?

Simple, use relative layout with the set margin code. Set the margins between each text view, button, etc and it will look the same on every phone. android:layout_marginTop="10dp" // change Top to Bottom, Left or Right for what you need.

How do I stop system font size changing effects to Android application?

Prevent-system-font-size-changing-effects-to-androidForms Android MainActivity. cs, override the Resources and set the configuration to default to restrict the font size effect on application. Resources. UpdateConfiguration() has been deprecated in API 25.


1 Answers

NOTE

I strongly discourage applying such a solution. In a certain way, Screen Zoom just "emulates" different screen sizes and densities in the same device. So, if your app can't handle well a specific screen zoom level, it means that your app may not be displayed correctly on a real screen out there. If your app can't support screen changes, tell the user about it...

There are some docs about screen sizes in the Android Developer and that's how you should handle different screen sizes.

On Android 12, it seems this the context created via context.createConfigurationContext(configuration) is imuttable. So, you may have problems on Android 12 when rotating the device, for example. context.getResources().getxxxxx() may return portrait resources (because the context was created in portrait) instead of landscape resources (the new orientation)

Support Different Screen Sizes

supports-screens-element

The answer below is just a "hack" where I tried to circumvent the screen zoom feature. I don't use that on my apps and I strongly recommend dealing with the screen zoom in a more conventional way.

Answer

My first answer does not work if you change the screen resolution. On Samsung devices, you can change the screen zoom but you can also change the screen resolution on some models (Settings->Display->Screen Resolution-> HD, FHD, WQHD etc).

So, I came up with a different code which seems to work with that feature as well. Just, please, note I can't fully test this code since I don't have too many devices to test. On those devices I tested, it seems to work.

One additional note. Ideally, you don't need to use such kind of code to circumvent the screen zoom. In a certain way, the screen zoom is just "simulating" bigger or smaller screens. So, if your app properly supports different screen sizes, you don't need to completely "disable" the screen zoom.

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

ORIGINAL ANSWER

You can try following code (overriding attachBaseContext). This will "disable" the screen zoom in on your app. This is the way to re-scale the whole screen at once.

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

On that code, I check if the current density is different from the Device's default density. If they are different, I create a new context using default density (and not the current one). Then, I attach this modified context.

You must do that on every Activity. So, you can create a BaseActivity and add that code there. Then, you just need to update your activities in order to extend BaseActivity

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

Then, in your activities:

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
}

I tested this code with:

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

Different Screen Zoom (using that code):

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

Different Screen Zoom (without that code):

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

So, using that code, I can get the same dimension in pixels regardless of the zoom level.

Edit

like image 142
W0rmH0le Avatar answered Sep 21 '22 08:09

W0rmH0le