Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does DisplayMetrics.scaledDensity actually return in Android?

Tags:

android

I'm trying to understand the sp unit in Android, but I can't really figure out how it works. The documentation say:

sp

Scale-independent Pixels - This is like the dp unit, but it is also scaled by the user's font size preference. It is recommend you use this unit when specifying font sizes, so they will be adjusted for both the screen density and the user's preference.

Using DisplayMetrics it is possible to get the scaledDensity which is:

A scaling factor for fonts displayed on the display. This is the same as density, except that it may be adjusted in smaller increments at runtime based on a user preference for the font size.

So given this information I assume that the scaledDensity will change as I change the system font size and that I can use this information to know the ratio between 1dp and 1sp (scaledDensity / density).

However when I'm testing this on my phone (Nexus 4) I am not getting the results I'm expecting. DisplayMetrics.scaledDensity always returns the same value (equal to DisplayMetrics.density) regardless of what font size I have set my phone to use (via settings -> accessibility -> use large font or settings -> display -> font size).

When I change the font settings my sp specified fonts changes size, so the sp unit works as expected but the scaledDensity value doesn't change at all.

The code I'm using to get the scaledDensity is:

DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
metrics.scaledDensity

Am I doing something wrong or have I misunderstood what DisplayMetrics.scaledDensity actually does?

Update

Here are three screenshots that illustrates what I mean:

enter image description here

And this is the code for the application:

DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);

String result = "\n"
    + "density : " + metrics.density + " (24 dp = " + 24 * metrics.density + " px)\n"
    + "scaledDensity: " + metrics.scaledDensity + " (24 sp = " + 24 * metrics.scaledDensity + " px)\n";

((TextView) this.findViewById(R.id. label)).setText(result);
((TextView) this.findViewById(R.id. sp)).setTextSize(android.util.TypedValue. COMPLEX_UNIT_SP , 24);
((TextView) this.findViewById(R.id.dp )).setTextSize(android.util.TypedValue. COMPLEX_UNIT_DIP, 24);

I want to be able to calculate the amount of pixels needed y-wise to draw a certain string. If I specify the size of the string using dp I can easily calculate the required pixels using displayMetrics.density. But when I use sp and try to calculate the amount of pixels using displayMetrics.scaledDensity I do not get the correct values.

like image 353
nibarius Avatar asked Apr 07 '14 15:04

nibarius


People also ask

What is scaledDensity?

DisplayMetrics#scaledDensityA scaling factor for fonts displayed on the display. This is the same as density, except that it may be adjusted in smaller increments at runtime based on a user preference for the font size.

What is XDPI and YDPI?

The exact physical pixels per inch of the screen in the X dimension. ydpi. The exact physical pixels per inch of the screen in the Y dimension.


2 Answers

I was able to figure out this myself. After digging trough the Android source code I found that the scaledDensity property is calculated as scaledDensity = density * fontScale where fontScale depends on the system setting for font size. So this was just as I thought it should be.

The reason why scaledDensity always had the same value for me regardless of font size was because I was using the DisplayMetrics for the wrong Display. I got the DisplayMetrics from the default display and regarding this the documentation say:

Returns the Display upon which this WindowManager instance will create new windows.

Despite the name of this method, the display that is returned is not necessarily the primary display of the system (see DEFAULT_DISPLAY). The returned display could instead be a secondary display that this window manager instance is managing. Think of it as the display that this WindowManager instance uses by default.

In this case the default display is not the same display as the primary display of the system, so when I change the system font setting that setting is changed on the primary display and not on the default display. If I update my code to get the DisplayMetrics for the display that is actually used scaledDensity returns the expected values scaled according to the system font settings.

With the following updated code that uses getResources().getDisplayMetrics() everything works as expected:

DisplayMetrics metrics = getResources().getDisplayMetrics();
String result = "\n"
    + "density : " + metrics.density + " (24 dp = " + 24 * metrics.density + " px)\n"
    + "scaledDensity: " + metrics.scaledDensity + " (24 sp = " + 24 * metrics.scaledDensity + " px)\n" ;

((TextView) this.findViewById(R.id.label)).setText(result);
((TextView) this.findViewById(R.id.sp)).setTextSize(TypedValue.COMPLEX_UNIT_SP, 24);
((TextView) this.findViewById(R.id.dp)).setTextSize(TypedValue.COMPLEX_UNIT_DIP, 24);

With this way of getting the display metrics this is the result I get (observe how the scaled density changes with font size):

enter image description here

like image 163
nibarius Avatar answered Nov 03 '22 00:11

nibarius


Scaled density is a unit of measurement not a value of font-size. Let me try to give you an example, feel free to follow up with a comment if you would like more explanation.

Say you have a font size 18sp. This needs to get converted to pixels in order to display on the screen. The following calculation is made: pixels = fontSize * scaledDensity * scale. Now, when you change your phone's settings, you are changing the scale. This results in the same calculation being made, but let's say with a scale of 1.5 now instead of 1. The unit of scaledDensity is not changed because your screen's pixel density has not changed.

I hope that helps!

like image 3
Dan Harms Avatar answered Nov 03 '22 02:11

Dan Harms