83

I recently finished developing my android application. I used sp (Scaled pixels) for all textSize. The problem is when i adjusted the system font-size, my application's font-sizes are changing. I can use dp (Device independent pixels) but it will take too long to maintain my application.

I referenced text size from this.

Is there a way to prevent system font-size changing effects to my application ?

james
  • 1,967
  • 3
  • 21
  • 27

13 Answers13

92

If you require your text to remain the same size, you'll have to use dp.

To quote the documentation:

An sp is the same base unit, but is scaled by the user's preferred text size (it’s a scale-independent pixel), so you should use this measurement unit when defining text size (but never for layout sizes).

Emphasis mine.

So you're seeing the expected behaviour for using sp as your units for text size.

I don't understand what you mean about using dp taking too long to maintain your app - as far as I can tell, it'll exactly be the same amount of effort? (perhaps less, though it'll likely make it less usable for users with poor eyesight)

ivan
  • 1,177
  • 8
  • 23
Adam S
  • 16,144
  • 6
  • 54
  • 81
  • I agreed your explaination. But in IOS it restricted to specific applications like calendar, mails, ect by the System. For Android, there is not restriction. So can i prevent my application from changing system font-szie. – james Feb 04 '14 at 08:05
  • 1
    You cannot stop the user from changing the system font size; you'll just have to use `dp` and ignore the lint errors! There's a [blog post on how to disable lint errors](http://tools.android.com/recent/ignoringlintwarnings) which might be useful. – Adam S Feb 04 '14 at 08:10
  • 3
    While your usage may be very specific and require a constant font size, I'd recommend considering how you can accommodate varying font sizes in your layouts, rather than just avoiding it altogether. It'll make for a much better experience for your users. – Adam S Feb 04 '14 at 08:19
  • 3
    If you have a working app, switching all `sp` values to `dp` values is error prone and requires more time to do so. Then if chose to switch back to support it again, you need to revert all these changes. It just makes more sense to override `fontScale` in a parent activity. This requires less effort than changing resources. It is also better maintainability. Answer below overriding `Context` is the way to go. – parohy Apr 07 '21 at 05:18
  • Thank you for this great answer. – Wowo Ot Jul 25 '21 at 17:56
  • 1
    there should be a simple setting that developers can use in android manifest to not follow the system text and display size setting. – chitgoks Oct 09 '21 at 12:35
  • Dp cant be used in jetpack compose for text units – harish Padmanabh Apr 13 '22 at 11:16
64

I recently ran into this problem as well. Our UI didn't scale well on phones with limited screen dimensions and changing the entire UI on the off chance a user set's their Accessibility Options to "Huge" seemed silly.

I found this question on StackOverflow to be most helpful.

What I did was put the following code below in my BaseActivity (an Activity class that all my activities extend from)

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

And called it right after my super.onCreate() like so

adjustFontScale(getResources().getConfiguration());

What this code does is identify if the user set their font scale in Accessibility Settings to something greater than 1.30f (1.30f is "Large" on The Note 5, but probably varies a bit from device-to-device). If the user set their font too large ("Extra Large", "Huge"...), we scale the application only to "Large".

This allows your app to scale to a user's preferences (to a degree) without distorting your UI. Hopefully this will help others. Good luck scaling!

Other Tips

If you want certain layouts to scale with your fonts (say...a RelativeLayout that you use as a backdrop against your fonts), you can set their width/height with sp instead of the classic dp. When a user changes their font size, the layout will change accordingly with the fonts in your application. Nice little trick.

dephinera
  • 3,703
  • 11
  • 41
  • 75
welshk91
  • 1,613
  • 18
  • 16
  • Can I ask why your example is slightly different from the question you linked, regarding this line: `WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);`. I'm new to android and am trying to pick the 'best' answer. Thanks! – Tallboy Mar 12 '16 at 22:55
  • 2
    @Tallboy good question. I've seen code get the window manager both ways (using the getSystemService call like I did and using getWindowManager() like the example I linked to). I just tested my code both ways and they both worked fine. I believe I read somewhere that getWindowManager returns a read-only Window Manager object and that may be a key difference between the two methods, but for this example it didn't matter. – welshk91 Mar 15 '16 at 00:39
  • 3
    This doesn't work on Nougat. Upto Marshmallow it works fine. – Nayan Mar 29 '17 at 11:30
  • I've looked across the entire web for days, and this is the only solution that works with Capacitor! Thanks! – nachshon f Oct 20 '20 at 19:09
  • updateConfiguration method is deprecated! – Suneesh Ambatt Jul 01 '21 at 06:32
  • This stopped working for me with Capacitor 3. – nachshon f Jul 29 '21 at 22:28
60

None of the previous answers worked for me, on Android 8.1 (API 27). Here's what worked: Add the following code to your activity:

Kotlin Code:

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

    super.attachBaseContext(newBase)
}

Java Code:

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

    super.attachBaseContext(newBase);
}

You don't need to change your AndroidManifest.xml.

Robert
  • 1,660
  • 22
  • 39
diogenesgg
  • 2,601
  • 2
  • 20
  • 29
  • 3
    This is the solution for Android API > N, Not tested on devices below N. – Dr. DS Apr 18 '20 at 14:33
  • java.lang.IllegalStateException: getResources() or getAssets() has already been called – Hiren Patel Nov 20 '20 at 06:00
  • @Dr.DS is it API > N or API >= N ? For me, it's not working on N. Just wanted to confirm – Sethuraman Srinivasan Nov 30 '20 at 11:24
  • @Dr.DS I have tested in below N device which working good – Eddie Brock Jan 21 '21 at 18:24
  • @diogenesgg working fine but activity is being recreated when I change font/zoom level while app is in background that's why I lost data in dialog and other calculations, I'm also not able to call onconfigurationchanged() method from base activity. – Naseer Attari Jan 25 '21 at 08:26
  • @applyOverrideConfiguration was not found – Nouman Shah Aug 21 '21 at 09:35
  • 1
    Confirm it works on Android 12. But `super.attachBaseContext(newBase);` need to moved to last line of attachBaseContext() – Robert Sep 08 '21 at 03:51
  • it is working like a charm, this answer saved my time. thumbs up – basaveshwar lamture Sep 27 '21 at 18:11
  • This is working fine. I have used it in an Activity. Can we use it in BaseAactivity? – Maulik Dodia Feb 25 '22 at 10:22
  • I'm using Capacitor v3 to build out my Android app and having the same issue. My MainActivity.java file only has 3 lines of code and I'm not very familiar with Java. How do I implement the above code in my project? --MainActivity.java-- package com.domain.app; import com.getcapacitor.BridgeActivity; public class MainActivity extends BridgeActivity {} – stupidFace Jun 30 '22 at 15:17
  • 1
    For Java you can use `override.setToDefaults();` to make sure that the default setting for each device will be reset, rather than having t o worry about the different settings for each device. – Eiri Sep 26 '22 at 20:27
  • I added this and got a problem with onConfigurationChange method. That wouldn't update my screen orientation on rotation. It seems adding this to activity prevent further updates on configuration. – mmdreza baqalpour Jun 13 '23 at 19:59
  • @mmdrezabaqalpour I also had that problem and assigning the font scale directly to the newBase did the job. override fun attachBaseContext(newBase: Context?) { newBase?.resources?.configuration?.fontScale = 1.0f super.attachBaseContext(newBase) } – Chuy47 Jul 28 '23 at 20:34
11

That's how we do it. In Application class override onConfigurationChanged() like this. If you want different behavior for different activities - override onConfigurationChanged() in Activity.

Don't forget to add manifest tag android:configChanges="fontScale" since you are hadnling this configuration change yourself.

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

In some helper class we have adjustFontScale() method.

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

WARNING! That will totally ignore Accessibility Font Scale user settings and will prevent your App fonts scaling!

localhost
  • 5,568
  • 1
  • 33
  • 53
6

That's how you do it in 2018 (Xamarin.Android/C# - same approach in other languages):

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

All you need is override attachBaseContext method of activity and update config there.

getBaseContext().getResources().updateConfiguration() is deprecated though there're numerous examples with this method. If you use this approach besides the IDE warning you might find some parts of your app not scaled.

Maxim Saplin
  • 4,115
  • 38
  • 29
4

There's another way to prevent app layout issue / font issue from the setting font size change. You can try

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

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

where newBase is from attachBaseContext function. You need to override this callback in your Activity.

But, the side effect is that if you wanna use animation (objectanimator/valueanimator), then it will cause the weird behavior.

Chauyan
  • 157
  • 8
3

you can force to text size of your app using base activity Configuration, make all activities inherent base activity. 1.0f will force the app font size to normal ignoring system settings.

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);
}
Usama Saeed US
  • 984
  • 11
  • 18
3

I encountered the same problem and fixed it by changing sp to dp in .XML file. However, I also need to fix the text size of the WebViews.

Normally, to adjust the text size of the WebView the setDefaultFontSize() function is used. However, its default value unit is sp.

In my project, I used setTextZoom(100) to fix the text and icon size of the WebView.* (There are other methods in stackoverflow but almost all of them are deprecated)*

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

For further details about setTextZoom()

emrcftci
  • 3,355
  • 3
  • 21
  • 35
mzdogan
  • 91
  • 1
  • 12
2

Tried answers about disabling fontScale in whole application. It's working, but I come to answer it's a terrible idea for only one reason:

You don't make your app better for visually impaired people.

Better way (I think) it's allow font scale but with restrictions only in some places, where you can't scale your text for it looks readable.


Realization (10.02.22)

After a day of thinking, I created Kotlin extension for TextView (also may use for EditText because it's a child):

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

Need divide scaledTextSize twice because after setting a new textSize for TextView font scale will happen.


UPDATE / FIX (14.02.22)

Previous solution doesn't work on real devices, only emulator (vice versa it overwise increasing text size). So, I found another way:

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

Here new textSize for TextView after setting will not be scaled on fontScale value.


Use example:

titleText.removeFontScale()
subtitleText.removeFontScale()

Logs of working, size in 1.3/1.5 scale become like in 1.0 scale:

  • Old realization (10.02.22):

    fontScale: 1.0 | oldSize: 20    
    fontScale: 1.3 | oldSize: 26 | newSize: 20.0
    
  • New realization (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
    

P.S. I notice that standard Toolbar didn't scale fonts and after that I understand that this practice is OK (disable scaling where it's really need).

SerjantArbuz
  • 982
  • 1
  • 12
  • 16
  • Regarding "You don't make your app better for visually impaired people.", It's sometimes the opposite. Android support for xsmall and xbig font sizes is almost null and most views will simply cut the text to some degree. Autoshrink components, miltiline, and width & height calculations don't work properly Sometimes it will become worse for the user than helpful. – htafoya Mar 04 '22 at 16:54
0

I think usage of dp is the best way, but in some case you may want to use a font style. However, the style is using sp, you can convert sp to dp by:

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

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

So, you can use the sp value from style without dynamic font size, and no need to hardcode the font size

mzdogan
  • 91
  • 1
  • 12
fung
  • 441
  • 5
  • 5
0

I think the closest best answer is from the user below "If you have a working app, switching all sp values to dp values is error prone and requires more time to do so. Then if chose to switch back to support it again, you need to revert all these changes. It just makes more sense to override fontScale in a parent activity. This requires less effort than changing resources. It is also better maintainability. Answer below overriding Context is the way to go. – parohy" Apr 7, 2021 at 5:18

0

I used this answer before, but this wouldn't update screen orientation on rotation.

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

    super.attachBaseContext(newBase);
}

I modify some code, this will update screen orientation on rotation.

@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);
}
  • I also had the same problem with rotation when using applyOverrideConfiguration, in my case assigning the font scale directly to the newBase did the job. override fun attachBaseContext(newBase: Context?) { newBase?.resources?.configuration?.fontScale = 1.0f super.attachBaseContext(newBase) } – Chuy47 Jul 28 '23 at 20:21
0

Assigning the fontScale directly to the 'newBase' context in the attachBaseContext method of the activity did the job for me

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)
}
Chuy47
  • 2,391
  • 1
  • 30
  • 29