How Do Ruler Apps Stay Accurate on All Devices

How do ruler apps stay accurate on all devices?

As you have discovered, iOS has no public API for getting the device's PPI.

These apps probably have a hardcoded list, as you speculated. There are under a hundred hardware device ids (see this list for example), and only a handful of different PPIs:

  • non-retina iPhone, iPod touch, and iPad mini: 163 PPI
  • retina iPhone (except 6+), iPod touch, and iPad mini: 326 PPI
  • iPhone 6+: 401 PPI
  • non-retina iPad: 132 PPI
  • retina iPad: 264 PPI

I'd guess the makers of these apps don't usually test their apps with every device. And unless you've checked the apps, there's no reason you should believe the apps are more accurate than the PPIs listed above.

To detect the iOS device type, check the answers to this question.

Why Lint shows warning when using in (inch) or mm (millimeter) units as dimension?

To be able to give a good answer to your question I first have to give an explanation on how DPI works in Android, so this answer is a bit long.

TL;DR: The documentation is correct, however the warning from Lint is also correct. That is there are some (buggy?) devices where mm, in and pt doesn't work correctly.

DPI in Android

In Android there are two different kinds of density measures that can be accessed from the DisplayMetrics class:

  • densityDpi - The screen density expressed as dots-per-inch. This value is always set to one of the generalized density buckets available in Android (e.g. Medium/160, High/240, Extra High/320).
  • xdpi / ydpi - The exact physical pixels per inch of the screen in the X/Y dimension. It is not uncommon that this value is slightly different than densityDpi depending on what screen size and resolution the device have.

The dp unit is calculated based on the densityDpi metric while the mm, in and pt units are all calculated based on xdpi/ydpi instead.

Reported values

It is possible to divide devices into four different categories depending on what values they report for densityDpi and xdpi / ydpi:

  1. densityDpi is almost the same, or the same as xdpi / ydpi.
  2. densityDpi is noticeably smaller than xdpi / ydpi
  3. densityDpi is noticeably larger than xdpi / ydpi
  4. the xdpi / ydpi values are completely wrong.

Category 1 (densityDpi is the same as xdpi / ydpi)

Example devices: Samsung Galaxy Trend, Nexus 4, Nexus 7.

For these devices there it doesn't really matter if you use in, mm, or dp. If you draw a square with sides 1 in and one with 160 dp they will be the same and both will be about 1 inch on the actual screen.

Category 2 & 3 (densityDpi is different than xdpi / ydpi)

Category 2 example devices: HTC One S, Sony Xperia V, Sony Xperia Z Ultra.

Category 3 example devices: Samsung Galaxy S4, Sony Xperia Z1, Nexus 5.

For these devices the densityDpi and xdpi / ydpi differs because the densityDpi must be set to one of the available density buckets. So the true physical dpi is rounded to match the nearest bucket and this is what is reported as densityDPI value. This is illustrated by Figure 1 in the Supporting Multiple Screens document.

This means that if you draw a square with side 1 in and one with 160 dp they will have slightly different size. For category 2 devices the 160 dp square will be slightly smaller and for category 3 devices it will instead by slightly larger. If you take out a ruler and physically measure the squares on the screen of the phone you will see that the 1 in square is 1 inch while the 160 dp square is a bit smaller or bigger depending on category.

This is a natural consequence of the density buckets system in Android, but unless you have a deeper understanding of how this works it may be a bit surprising that things drawn with a fixed dp measure may look different on different phones.

Category 4 (incorrect xdpi / ydpi values)

Example devices: Samsung Galaxy Mini, Samsung Galaxy S3 Mini.

These devices are more problematic, they have a good value for densityDpi (their true dpi rounded to the nearest bucket), but they report bogus values for xdpi / ydpi.

For example Samsung Galaxy S3 Mini reports an xdpi of 160 and densityDpi of 240. The screen 480 px wide so if 160 dpi was correct this would mean a screen width of 3 inch, but in reality the screen with is 2.05 inch. Based on these numbers the actual xdpi value that the phone should be reporting is 234.

Another example is the Samsung Galaxy Mini which also reports an xdpi of 160, but has densityDpi set to 120. That screen is 240 px wide so that would give a physical width of 1.5 inch, but in reality the screen is 1.9 inch (actual xdpi of 126).

So on these types of devices it is not possible to trust the in, mm, pt units since they will result in things being way too small or way too big.

Examples

Category 1-3

Comparison of category 1-3 devices

This is three screenshots of a test app I've made taken on devices from category 1, 2 and 3. The green square is drawn to be 1 inch big based on using the xdpi and ydpi values while the red square is drawn to be 1 inch big using dp (160 dp).

When physically measuring the result with a ruler the green square was inch big in all these examples while the red square size differed somewhat as it's visible on the screenshots.

Category 4

Screenshot from category 4 device

This is a screenshot from a category 4 device. The green and red squares are the same as in the previous example, but here I have also added two more squares, a yellow square that is 1 in and a blue square that is 72 pt. As visible on the screenshot all squares except the red square are of equal size.

When physically measuring the result with a ruler the red square is about one inch big while the rest of the squares are only about 0.67 inch big.

The answer to the question

The warning from Lint refers to category 4 type devices that return incorrect values for xdpi and ydpi. Since xdpi / ydpi can not be trusted on these devices the units (mm, in, pt) that depend on them can not be trusted either.

Is documentation wrong?

No the documentation is correct, these units are based on the physical size of the screen. However some problematic devices report wrong values for the physical size of the screen (xdpi / ydpi) to Android and then these units will be wrong as well.

And why Lint doesn't warn when using pt?

The Lint developers probably just forgot about pt, it is just as problematic as in and mm.

The reason Lint recommends using dp instead is because this is a unit that is used extremely frequently in Android so if any device had broken dp units pretty much all applications would look horrible and it would be fixed before the device was ever released to market.

Drawing a ruler in iOS - accurate 1 cm on screen - how to draw lines 1 mm apart?

iPhones (with the possible exception of the iPhone6+) are 163 "logical" points per inch. Obviously phones from 4 onwards have double or more the resolution but that doesn't make any difference for the coordinate system.

1mm therefore is 163/25.4 or approximately 6.4. iPad is 5.2 points per mm, iPad mini is the same as iPhone.

-(void)drawRect:(CGRect)rect
{
[[UIColor blackColor] setFill];
float i;

float linesDist = 163.0/25.4; // ppi/mm per inch (regular size iPad would be 132.0)

float linesWidthShort = 15.0;
float linesWidthLong = 20.0;

for (i = 0, count = 0; i <= self.bounds.size.height; i = i + linesDist, count++)
{
bool isLong = (int)count % 5 == 0;

float linesWidth = isLong ? linesWidthLong : linesWidthShort;
UIRectFill( (CGRect){0, i, linesWidth, 1} );
}
}

You want to use a float for i in order to avoid rounding errors when adding the distances up and to avoid unnecessary conversions.

Why is widget small although using dp

Update:

Based on this page about dp size of devices and the link provided at the top of it, I've reached to this article how to calculate metrics of any device including dp. Based on my calculations Note 10+(3040*1440 pixel, 495 ppi) is a 465 * 982 dp device. Google pixel you can see from the first link is a 411 * 731 dp device. So, if you create a size 200 dp layout it would be smaller on the note 10 + than on the google pixel for example. To be honest I thought all small screen devices are something close to a 360 width dp and expected that one design by dp would be seen roughly the same on all devices. I was wrong apparently. It seems if the layout is supposed to be seen exactly the same width on all devices there is no way but to set its width by a percentage of screen width. Google doc has it too: converting pixel to dp.

Sample Image

This image from Support different pixel densities lead me to incorrectly think that designing by dp would be seen the same on all devices. But it would be seen the same only on same dp devices.

Putting screen densities into the correct bucket

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):

generalized densities

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.

density buckets of some Android devices

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.

Why does my android emulator, and my android phone give me different results for a devices density and DPI?

but can anybody explain why an emulated version is more accurate than the real device?

It's not. The device is what the device is.


On DisplayMetrics, xdpi and ydpi are the actual physical density values. For example, the documentation for xdpi has:

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

In your question, you state that the device has "a real PPI of 258". That fits the values that you are getting from DisplayMetrics, bearing in mind that pixels are rarely square, so the xdpi and ydpi values are rarely exactly equal.


The value for density is based on a manufacturer setting (ro.sf.lcd_density in /system/build.prop, I think). Why LG decided to go with xhdpi instead of hdpi, I cannot say. If I had to guess, they felt that existing apps looked better on the device with that logical density. The emulator will use its own algorithm. Another manufacturer with a similar screen might choose hdpi (what the emulator chose).

The value for densityDpi is driven directly from density.



Related Topics



Leave a reply



Submit