I am writing an Android game and I am testing it in two devices at the same time:
Huawei P10 Lite: 5.2", 1920x1080, 424dpi, xhdpi.
Huawei P40 Pro:
6.58", 2640x1200, 441dpi, xhdpi.
These two devices are utterly different, as they differ in screen size and resolution, but NOT in dpi and dpi bucket; both are xhdpi.
That means regardless the dimen files I create (dimen-mdpi, dimen-hdpi...), any graphic element looks the same in both phones, which completely breaks the layout and proportions in the small phone.
I'm taking this as a reference for bucket classification:
Well dpi is all about resolution, and not screen size. If you want to create resources for screen sizes, and not screen resolutions, you should use smalest width qualifiers.
According to the docs:
One DIP is one pixel on an
approximately 160 dpi screen (for example a 240x320, 1.5"x2" screen),
providing the baseline of the system's display. Thus on a 160dpi screen
this density value will be 1; on a 120 dpi screen it would be .75; etc.
So if I understand correctly, you would convert from pixel to dp by: pixelSize / dip / 160. In this case the formula for your device width in dp is as follows: deviceWidthInPixels / (deviceDip / 160).
P10 width in dp: 1080 / (424 / 160) = 407.54
P40 width in dp: 1200 / (441 / 160) = 435.37
Looks like other people also came to this conclusion
A set of six generalized densities:
ldpi (low) ~120dpi
mdpi (medium) ~160dpi
hdpi (high) ~240dpi
xhdpi (extra-high) ~320dpi
xxhdpi (extra-extra-high) ~480dpi
xxxhdpi (extra-extra-extra-high) ~640dpi
From the wiki pages of each phone display
Scaling down into the buckets i.e if the ppi is 300 that would go into the hdpi bucket as it less then 320?
Galaxy S3 306ppi -> bucket hdpi
Nexus 4 318ppi -> bucket hdpi
Nexus 5 445ppi -> bucket xhdpi
Nexus 5X 432ppi -> bucket xhdpi
Nexus 6 493ppi -> bucket xxhdpi
Nexus 6P 518ppi -> bucket xxhdpi
Is this the correct way to work out buckets for screen sizes.
The reason I asked is because I have created the following value directory resources:
values-hdpi/dimens
values-xhdpi/dimens
values-xxhdpi/dimens
values-xxxhdpi/dimens
In the dimens.xml I have different margins and set the dp depending on the bucket size i.e.
<dimen name="network_quantity_margin_top">100dp</dimen>
I am interested to know if this is the correct way to do this.
The reason I asked is because I have created the following value directory resources. (...)
In the dimens.xml I have different margins and set the dp depending on the bucket size. (...)
I am interested to know if this is the correct way to do this.
I'm not sure why you want different margins specified in dp depending on the density. Specifying the margin as dp once, for the baseline density, already handles all other densities for you, meaning that the physical size of the margin will be the same when displayed on any device.
If you used px instead of dp (but don't), then you would have to do the scaling for different screens yourself.
Scaling down into the buckets i.e if the ppi is 300 that would go into the hdpi bucket as it less then 320?
Yes, but not because it is less than 320. If there was a rule of thumb I would say it is rounding to the nearest generalized density. See this illustration of how Android roughly maps actual densities to generalized densities (figure is not exact):
Relevant part of the documentation is this:
Each generalized size and density spans a range of actual screen sizes and densities. For example, two devices that both report a screen size of normal might have actual screen sizes and aspect ratios that are slightly different when measured by hand. Similarly, two devices that report a screen density of hdpi might have real pixel densities that are slightly different. Android makes these differences abstract to applications, so you can provide UI designed for the generalized sizes and densities and let the system handle any final adjustments as necessary.
So again, you shouldn't really care how Android does this if you are just writing an app. What you should care about is:
specify all layout dimension values in dp or with wrap_content/match_parent, as appropriate (text can be in sp to additionally match the user preference, but nothing other than text),
think about different layouts depending on physical size and orientation of the screen,
provide bitmap resources for different densities, just to avoid blurry or pixelated artifacts (because Android will scale them to have the right physical size if you use dp or wrap_content).
Android will lookup the best matching resource, and then transparently handle any scaling of the dp units, as necessary, based on the actual density of the screen in use. The conversion of dp units to screen pixels is simple: px = dp * (dpi / 160).
Note the actual density as opposed to generalized density. The latter is only a convenience for the developers, since it would be impossible to provide drawables for every screen out there. This way developers need to provide only 3 or 4 sets of graphics, while Android picks the closest fit and adjusts it further for the needs of that particular device. (Nowadays it's possible to use one vector drawable instead of many pre-scaled raster graphics, meaning better quality and less size.)
Is this the correct way to work out buckets for screen sizes.
No, it is not. According to Google device metrics all devices you listed fall into buckets higher than you expected:
Galaxy S3 NA NA
Nexus 4 318 xhdpi
Nexus 5X 424 xxhdpi
Nexus 5 445 xxhdpi
Nexus 6 493 xxxhdpi
Nexus 6P 515 xxxhdpi
I took some other devices from that list, and plotted how different devices are falling into density buckets depending on their actual physical density.
Chromebox 30 101 mdpi
Chromebook 11 135 mdpi
Samsung Galaxy Tab 10 149 mdpi
Nexus 7 '12 216 tvdpi
Android One 218 hdpi
Chromebook Pixel 239 xhdpi
Nexus 9 288 xhdpi
Nexus 10 299 xhdpi
Moto X 312 xhdpi
Nexus 4 318 xhdpi
Nexus 7 '13 323 xhdpi
Moto G 326 xhdpi
Dell Venue 8 359 xhdpi
LG G2 424 xxhdpi
Nexus 5X 424 xxhdpi
HTC One M8 441 xxhdpi
Nexus 5 445 xxhdpi
Nexus 6 493 xxxhdpi
Nexus 6P 515 xxxhdpi
LG G3 534 xxhdpi
You can see, that with some notable exceptions, the rule that the closest generalized density is selected, holds.
The exceptions being Nexus 6 and 6P, that are listed as xxxhdpi, even though LG G3 has a higher physical density and still is far from 640px/in. Android One is hdpi but it is only slightly denser than Nexus 7 '12 which is tvdpi. Chromebox 30 and Chromebook Pixel (admittedly, not Android) are assigned to buckets mdpi and xhdpi even though they are physically lower than ldpi and hdpi, respectively.
I am interested to know if this is the correct way to do this.
You are mostly correct.
The problem lies in this part:
The reason I asked is because I have created the following value directory resources:
values-hdpi/dimens
values-xhdpi/dimens
values-xxhdpi/dimens
values-xxxhdpi/dimens
In the dimens.xml I have different margins and set the dp depending on the bucket size i.e.
<dimen name="network_quantity_margin_top">100dp</dimen>
The purpose of dp gets defeated by defining folders like values-hdpi/dimens. Density Pixels, by design, are device-agnostic - 100dp on a device with dpi = 240 will look just as wide/long on a device with dpi = 480. So, if you want your app to look consistent, do not provide different dimensions for different screen densities.
The correct way to think about this is to realize that the only resource that is affected by varying screen densities is drawable. A drawable on a screen with dpi = 240 will look twice as big compared to a screen with density = 480. I am sure that you're providing folders like drawable-hdpi, drawable-xhdpi etc. to deal with this. For everything else, and especially dimensions, use dp. For text sizes, use scaled-pixels - sp.
More importantly, you should worry about the range of different screen sizes that are available for android. How would you use all the extra screen real-estate on a 10 inch device compared to a 5 inch phone? Qualifiers such as -normal, -large, xlarge should be of more interest to you.
To summarize:
consider all devices of a certain screen size the same - their screen densities are irrelevant when using density pixels.
for every drawable resource you use, place their scaled versions in the buckets you wish to support. Remember that, if you don't provide resources for a certain bucket (say drawable-hdpi), android will scale down your drawables from drawable-xhdpi folder (provided drawable-xhdpi is defined). The reverse is also true: if you have placed all your drawables in drawable-xhdpi, android would scale-up your drawables on a xxhdpi device. The result will be blurry graphics - because of scaling-up.
I know that its a bit of a steep slope here :). So, if you need to clarify some more, leave me a comment.
From the Android documentation:
Supporting Multiple Screens
In some cases, you will need to express dimensions in dp and then
convert them to pixels. Imagine an application in which a scroll or
fling gesture is recognized after the user's finger has moved by at
least 16 pixels. On a baseline screen, a user's must move by 16 pixels
/ 160 dpi, which equals 1/10th of an inch (or 2.5 mm) before the
gesture is recognized. On a device with a high-density display
(240dpi), the user's must move by 16 pixels / 240 dpi, which equals
1/15th of an inch (or 1.7 mm). The distance is much shorter and the
application thus appears more sensitive to the user.
To fix this issue, the gesture threshold must be expressed in code in
dp and then converted to actual pixels. For example:
// The gesture threshold expressed in dp
private static final float GESTURE_THRESHOLD_DP = 16.0f;
// Get the screen's density scale
final float scale = getResources().getDisplayMetrics().density;
// Convert the dps to pixels, based on density scale
mGestureThreshold = (int) (GESTURE_THRESHOLD_DP * scale + 0.5f);
// Use mGestureThreshold as a distance in pixels...
The DisplayMetrics.density field specifies the scale factor you must
use to convert dp units to pixels, according to the current screen
density. On a medium-density screen, DisplayMetrics.density equals
1.0; on a high-density screen it equals 1.5; on an extra-high-density screen, it equals 2.0; and on a low-density screen, it equals 0.75.
This figure is the factor by which you should multiply the dp units on
order to get the actual pixel count for the current screen. (Then add
0.5f to round the figure up to the nearest whole number, when converting to an integer.) For more information, refer to the
DisplayMetrics class.
you have to handle the resources regarding the screen width in dp not dpi(dot per inch)
for example nexus 5 1920 X 1080 480 dpi it uses xxhdpi
and nexus 6p 2560 X 1440 560 dpi it uses xxhdpi too not xxxhdpi !!
to handle it use the smalest width resource "drawable-swXXXdp"
width in dp = width in pixle / (dpi/160)
dp= 1440/(560/160) =~ 411
create drawable-sw411dp
Reference
When speaking about Android devices, PPI == DPI
PPI (Pixels Per Inch) and DPI (Dots Per Inch) mean the same thing since they are a measure of the density of the screen; thus, your approach about PPI vs DPI is correct.
A detailed explanation can be found here. The key part of the article is the following:
The screen density is quoted as Pixels Per Inch, PPI, and is the
number of pixels that fit into an inch. The higher the number then the
sharper images look on the display, therefore consumers consider a
high PPI figure an advantage when buying a device. Sometimes the
figure is quoted as Dots Per Inch, DPI ...
Bit late, but this may be useful to other readers. Screen density and the "bucket" Android uses to pick a resource from can be confusing. Google's documentation is pretty dense, and it takes alot of work to distill it down to something useful when writing code. Partly because there are several factors and they're telling you everything about screen density and dips. But the short answer is this.
Basically, dpi is your defining factor, (if you rely on other factors like small/medium/large), this is not your answer. Otherwise, I found this answer quite helpful and simple. Here is some code I have collated from various sources that I run on app startup to determine display information.
The screen density tells me what dpi level the device supports.
float density = context.getResources().getDisplayMetrics().density;
Next I have a simple device metrics method to tell me about the screen. (Note, I am using Timber logger).
protected static void checkDeviceSize(AppCompatActivity context) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
Display display = context.getWindowManager().getDefaultDisplay();
DisplayMetrics outMetrics = new DisplayMetrics();
display.getMetrics(outMetrics);
float density = context.getResources().getDisplayMetrics().density;
float dpHeight = outMetrics.heightPixels / density;
float dpWidth = outMetrics.widthPixels / density;
String dpiName = getDpiName(density);
Timber.e("density :" + density + " [" + dpiName + "]");
Timber.e("height dp:" + dpHeight + ", (" +outMetrics.heightPixels + "px)");
Timber.e("width dp :" + dpWidth + ", (" + outMetrics.widthPixels + "px)");
}
I also have this simple helper method that determines the DPI name to support the method above.
public static final String DPI_LDPI = "ldpi";
public static final String DPI_MDPI = "mdpi";
public static final String DPI_HDPI = "hdpi";
public static final String DPI_XHDPI = "xhdpi";
public static final String DPI_XXHDPI = "xxhdpi";
public static final String DPI_XXXHDPI = "xxxhdpi";
public static final String DPI_TVDPI = "tvdpi";
private static String getDpiName(float density) {
String result = "undefined";
if (density < 1.0) {
result = DPI_LDPI;
} else if (density == 1.0f) {
result = DPI_MDPI;
} else if (density <= 1.3f) {
result = DPI_TVDPI;
} else if (density <= 1.5f) {
result = DPI_HDPI;
} else if (density <= 2.0f) {
result = DPI_XHDPI;
} else if (density <= 3.0f) {
result = DPI_XXHDPI;
} else if (density <= 4.0f) {
result = DPI_XXXHDPI;
}
return result;
}
Finally this video from 2013 is still relevant today.
I have design for my project. It for Nexus 6 with resolution 1440px x 2560px and 493 ppi. My design - 1440px x 2560px.
I know that:
ldpi 120 dpi
mdpi 160 dpi
tvdpi 213 dpi
hdpi 240 dpi
xhdpi 320 dpi
xxhdpi 480 dpi
xxxhdpi 640 dpi
I have - 493 ppi. what's this? I have a button size 100x50 pixels on the design.
how many dpi need button to look as well as in the design?
Hope this helps: Getting Your Apps Ready for Nexus 6 and Nexus 9
Nexus 6
Screen
The Nexus 6 boasts an impressive 5.96” Quad HD screen display at a
resolution of 2560 x 1440 (493 ppi). This translates to ~ 730 x 410 dp
(density independent pixels).
Check your assets
It has a quantized density of 560 dpi, which falls in between the
xxhdpi and xxxhdpi primary density buckets. For the Nexus 6, the
platform will scale down xxxhdpi assets, but if those aren’t
available, then it will scale up xxhdpi assets.
EDIT:
Consider this line
2560 x 1440 (493 ppi). This translates to ~ 730 x 410 dp (density
independent pixels)
It implies the scaling factor is 2560/730 = 1440/410 ~ 3.5
For a screen of width 410 and height 730 in dp, if you want to specify a button of half the screen width in dp, the width should be 410/2 = 205 dp which translates to 717px = 205*3.5 (width in dp * scaling factor)
A button of size 100px x 50px would translate to 28 x14 dp (100/3.5 * 50/3.5) on a 410 x 730 dp screen size.
If you are genuinely concerned about proper display resolution (which it sounds like you are) then you should never rely on the generic folders. The folders are there for generalized purposes - not for customized or high value display.
You should use displayMetrics to measure the screen size and select the appropriate asset, or even do customized image scaling. There are a lot of resources around this set of properties, but essentially it allows you to address the issue you are facing: set appropriate margins, padding, resolution, layout, etc. for a highly customized display.
Here are the docs:
http://developer.android.com/reference/android/util/DisplayMetrics.html
And then some "simple" implementations of getting the metrics:
How to get screen display metrics in application class
getting the screen density programmatically in android?
Everywhere in Android documentation I can ready that I have to use sw600dp for 7 inch tablets, but this does not seem to be true, because Galaxy Tab 2 7" has 546dp for its smallest width.
http://www.gsmarena.com/samsung_galaxy_tab_2_7_0_p3100-4543.php
dp = 600 / (170 / 160) = 546
Should I use sw500dp in my layouts instead?
Android device pixel densities are classified in buckets:
While the Galaxy Tab 2 screen may actually be 170 dpi, that falls within the range of mdpi which translates to 160 dpi being used in practice for dp calculations. The device should still have a 600dp width.
I'm trying to understand which layout uses which device.
For instance, I've different layouts:
layout
layout-sw600dp
layout-sw720dp
Nexus 7" (800x1280) uses layout-sw600dp
Samsung GTp3100 7" (600x1.024) uses layout-sw600dp
Samsung Tab 10.1 (1280x800) uses layout-sw720dp
Sony XPERIA S 4.3 (1280x720) uses layout
Can you guys explain "with your words" why isn't XPERIA S using sw600dp or sw720dp?
SW != smallest width? So smallest with from XPERIA S is 720. It should be using sw720 or sw600, right? Or am I wrong?
I've read tons of documentation, but can't understand why is this happening.
Thanks a lot.
The Smallest-Width qualifier refers primarily to screen size and not the physical pixels.
Using sw720dp for example means the device must have a minimum screen width of 720 dp (device-independent pixels) - this isn't about 720 physical pixels. From the documentation...
The density-independent pixel is equivalent to one physical pixel on a 160 dpi screen, which is the baseline density assumed by the system for a "medium" density screen.
The physical width of the Sony XPERIA S 4.3 is 2.5" but I don't know how much of that is the actual width of the screen.
Let us pretend for the sake of calculations that the screen covers the full width...in this case to calculate dpi for width we simply use 720 / 2.5 = 288dpi.
From the documentation for dp...
The conversion of dp units to screen pixels is simple: px = dp * (dpi / 160).
Rearranging the formula above to calculate dp we do this...
dp = px / (dpi / 160)
...this gives us the width in dp (and a necessary Smallest-width qualifier) of...
720 / (288 / 160) = 400dp
In short the answer is, the Sony XPERIA S 4.3 may have a high-density screen (for its size) but it isn't a large screen.