【Android】いまさら聞けないdp入門

# dpとは？
Androidのデザインにおいて、必ず理解しておくべきポイントのひとつがdpについてです。
dp(density-independent pixels)とは密度非依存ピクセルのことで、dipとも呼ばれています。
Androidはこのdpのおかげで多用な端末をサポートできているのです。


dpのない世界 - 端末の解像度ごとに表示サイズが違う
![density-test-bad.png](https://qiita-image-store.s3.amazonaws.com/0/48019/16dad56d-7cbf-b1f9-40dc-760073838894.png)


dpのある世界 - 端末の解像度が違っても表示サイズは保たれる
![density-test-good.png](https://qiita-image-store.s3.amazonaws.com/0/48019/c2cd0d88-3de3-78da-fb6a-8fd35bd7eb3c.png)

[Supporting Multiple Screens | Android Developers](http://developer.android.com/guide/practices/screens_support.html#density-independence)
[More Resource Types | Android Developers](http://developer.android.com/guide/topics/resources/more-resources.html#Dimension)

## px、dpi、dp
dpを理解する上で欠かせない要素がpx(ピクセル)とdpi(画面密度)です。

### px
px(pixel)というのはハードウェアで実装されている解像度そのものです。ピクセルとも言います。
Androidでは端末によって画面解像度が大きく異なる場合があるため、pxで要素を表示すると端末の画面解像度に応じて表示される実サイズがまちまちになってしまいます。
Androidアプリ上でピクセルを直接扱う局面は限られており、ほとんどが画像編集とOpenGL関連です。

pxで要素のサイズを指定した例
![density-test-bad.png](https://qiita-image-store.s3.amazonaws.com/0/48019/06d18717-20cc-e13a-f07e-11926ac2bd88.png)

### dpi
dpi(dots per inch)は画面密度のことで、1インチの中に何ドット含まれているかを表しており、そのままScreen Densityと表現されることもあります。
同じフルHD解像度の端末でもスマートフォンとタブレットではこのdpiが異なっているため、Androidではpxとdpiを考慮したdpという単位が必要だったのです。
ただし、実際のdpiは端末ごとに異なるため、個別にサポートするのはあまり現実的ではありません。
そこで、Androidではdpiをある範囲ごとにランク分けすることにしました。
このランク分けされた画面密度は汎用密度(generalized density)と呼ばれています。

![density-ranges.png](https://qiita-image-store.s3.amazonaws.com/0/48019/7e92ca8b-4f2a-527a-826d-8d86a0ac8aec.png)

[Supporting Multiple Screens | Android Developers](http://developer.android.com/guide/practices/screens_support.html)
[Devices and Displays | Android Developers](http://developer.android.com/design/style/devices-displays.html)

### dp
dp(density-independent pixels)とは密度非依存ピクセルのことで、dipとも呼ばれます。
密度に依存しないピクセルとは、どの画面密度でも同じように表示できるピクセル単位ということです。
dpで指定した値を画面密度に応じて計算することで、実際の端末で表示するpxに変換することができます。
このpxを表示してみるとどの端末でも同じように表示されるため、密度非依存ピクセルと呼んでいるのです。
もちろんここでいう密度とは汎用密度のことなので、実dpiが汎用密度の基準dpiからずれている端末ではわずかに表示サイズに違いが出ます。
汎用密度ではこの問題を考慮し、細かく範囲を設定していますが、「厳密に同じサイズで表示されるわけではない」ということは覚えておく必要があります。

dpで要素のサイズを指定した例
![density-test-good.png](https://qiita-image-store.s3.amazonaws.com/0/48019/0f480516-e023-4f57-512c-2e0c8a1831c8.png)


汎用密度とdp、pxの関係は以下のようになっています。
ldpiの倍率が0.75倍なため、dpでの指定は4の倍数を使うのが良いでしょう。

|汎用密度|倍率|dp|px|
|:--:|:--:|:--:|:--:|
|ldpi|x0.75|4dp|3px|
|mdpi|x1|4dp|4px|
|hdpi|x1.5|4dp|6px|
|xhdpi|x2|4dp|8px|
|xxhdpi|x3|4dp|12px|
|xxxhdpi|x4|4dp|16px|

### sp
dpに似た単位として、sp(scale-independent pixel)があります。
これは文字サイズの指定に用いるもので、画面密度に加えてフォントサイズの設定も考慮されています。

## 端末のdp解像度
dpの大きな利点のひとつは、端末のdp解像度をある程度絞り込めることです。
汎用密度の範囲は端末の画面サイズをdpで表したときにうまく重なるように設定されているため、ほとんどの端末は一定のdp解像度範囲に収まります。
スマートフォンであれば、横幅は`320-410dp`の範囲に収まるとされています。
(以前は`384dp`まででしたが、Nexus6追加時に`410dp`に拡張されました)

試しに、いくつかのNexus端末を元に計算してみましょう。

|端末名|px解像度|汎用密度|dp解像度|
|:-:|:-:|:-:|:-:|
|Galaxy S|480 x 800|hdpi|320 x 534|
|Galaxy Nexus|720 x 1280|xhdpi|360 x 640|
|Nexus 5|1080 x 1920|xxhdpi|360 x 640|
|Nexus 6|1440 x 2560|560dpi|410 x 730|
|Nexus 5X|1080 x 1920|420dpi|410 x 730|
|Nexus 6P|1440 x 2560|560dpi|410 x 730|

[アプリを Nexus 6 と Nexus 9 に備えましょう - Google Developer Japan Blog](http://googledevjp.blogspot.jp/2014/11/nexus-6-nexus-9.html)
[Android 6.0 Marshmallow: Nexus 端末へ配信開始 - Google Developer Japan Blog](http://googledevjp.blogspot.jp/2015/10/android-60-marshmallow-nexus.html)

このように、実解像度の違いに関わらず、dpでみた横幅は6台とも`320-410dp`の範囲に収まっていることがわかります。
`320-410dp`で崩れないレイアウトを心がけておけばほとんどの端末で崩れることはありません。

最近発売された端末のdp解像度の傾向は以下にまとめてあります。
[2014年発売Android端末のdp解像度まとめ - Qiita](http://qiita.com/nein37/items/3918f5833bfa31fbe3d5)
[2015年発売Android端末のdp解像度まとめ - Qiita](http://qiita.com/nein37/items/0a622f7ebbbb92db93d5)

## dpと画像
Androidではアイコンなどの画像を利用する際、[汎用密度ごとに適切な解像度の画像を用意する](http://developer.android.com/intl/ja/guide/topics/resources/providing-resources.html#AlternativeResources)ことが推奨されています。
これらの画像も一旦dpでデザインし、各画面密度に必要なピクセルを計算したほうが良いでしょう。

例として、以下のようなアイコン画像を用意する場合をあげます。
縦横`48dp`で上下左右に`8dp`ずつの余白を持たせ、内側`32dp`をデザイン領域としています。
![metrics_icon.png](https://qiita-image-store.s3.amazonaws.com/0/48019/995b39f0-be86-7c32-9d7f-79503331eb1b.png)

このアイコンを汎用密度ごとに用意する場合、以下の様にピクセル数が決まります。

|汎用密度|倍率|縦横のサイズ|上下左右の余白|デザイン領域のサイズ|
|:--:|:--:|:--:|:--:|:--:|
|ldpi|x0.75|36|6|24|
|mdpi|x1|48|8|32|
|hdpi|x1.5|72|12|48|
|xhdpi|x2|96|16|64|
|xxhdpi|x3|144|24|96|
|xxxhdpi|x4|192|32|128|


## 難点
dpではできないこと、難しいこともあります。
Androidでは汎用密度で計算したdpによる画面サイズが端末ごとに大きく変わらないという法則によってレイアウトを行います。
しかし、物理的に極端に小さい端末や汎用密度の基準から極端に外れた端末ではdp換算された画面解像度が大きく異なる場合があります。
イレギュラーな端末なのであまり考慮する必要はありませんが、サポート対象端末選定の際には該当端末のdpiとdp解像度を意識したほうが良いでしょう。

# スマートフォンとdp
スマートフォンにおけるdpの注意点やTipsをまとめます。
国内での主流端末の解像度はおもにハーフHD(1280x720)、フルHD(1920x1080)、WQHD(2560x1440)になっており、いずれも360dp x 640dpです。
しかし4インチ台の320dp端末も新機種が発売されており、直近で発売されたNexus端末は410dpを採用しています。
このことから、320-410dp範囲の対応は必須と言えるでしょう。


## レイアウトの決め方
前述の通りスマートフォンの画面幅は`320-410dp`なので、まず`320dp`で配置します。

`320dp`で各要素を配置できたら、`410dp`の場合にどのようなレイアウトになるかを考えます。
各要素を伸縮させたり余白を広げたりして不自然なレイアウトにならないように工夫しましょう。
このとき、`RelativeLayout`を使ったり`LinearLayout`の`android:layout_weight`を使うと自然に対応することができます。
スマートフォンでの横向き表示に対応する場合、横幅が`730dp`に達する端末もあるので別レイアウトにするかどうかも考慮したほうが良いでしょう。
詳しくはタブレット対応の項を参照してください。

縦方向については`480dp-730dp`におさまる場合が多いですが、横幅と違い端末のアスペクト比によってかなり差があります。
`ListView`や`GridView`などの縦スクロール要素をうまく使って余白が開きすぎないように調整しましょう。
縦`480dp`で見切れないレイアウトであっても画面回転に対応する場合はスクロール領域を入れておくべきです。

##よくある工夫
リソースを最小短辺サイズ(`sw360dp`など)で分割することにより、短辺があるサイズ以上の端末のみに代替リソースを提供することが出来ます。
最小短辺サイズでリソースを分割する際はなるべくdimenを分割するようにしましょう。
layoutを分割してしまうと、後々管理が大変になりがちです。

# タブレットとdp
タブレットにおけるdpの注意点やTipsをまとめます。
国内では最近のタブレットの解像度はアスペクト比16:10の`1280x800px`や`1920x1200px`、`2560x1600px`が主流ですが、画面サイズが大きいためスマートフォンとは汎用密度が異なることに注意する必要があります。
Android Developersによると、dp解像度では短辺が`600-800dp`、長辺が`960-1280dp`の範囲におさまる想定になっています。
![metrics_tablet.png](https://qiita-image-store.s3.amazonaws.com/0/48019/c18f233b-6b0d-3895-383d-346f929b3655.png)

タブレットのdp解像度をいくつか算出してみましょう。

|端末名|px解像度|汎用密度|dp解像度|
|:-:|:-:|:-:|:-:|
|Nexus 7(2012)|1280 x 800|tvdpi|962.4 x 601.5|
|Nexus 7(2013)|1920x 1200|xhdpi|960 x 600|
|Nexus 10|2560 x 1600|xhdpi|1280 x 800|
|Nexus 9|2048 x 1536|xhdpi|1024 x 768|

このようにアスペクト比の異なるNexus 9も含め、全て範囲内におさまりました。
Nexus 7(2012)のtvdpiは x1.33 という倍率を持った特殊な汎用密度です。
通常はmdpiとhdpiのリソースが必要に応じて流用されるため、これを直接ターゲットにして代替リソースを提供することは推奨されていません。

## スマートフォンとの違い
タブレットの判定方法は、実は2種類あります。
現在は短辺600dp以上(`sw600dp`)という条件が利用されることが多いですが、API Level 13で追加されたフラグのため、これ未満のタブレットでは適用できません。
そのため、画面物理サイズに応じた指定(large/xlarge)を併用する場合があります。
`res/layout-xlarge`(API Level 13未満)
`res/layout-sw600dp`(API Level 13以降)
ただし、国内では現状API Level 13未満のタブレットがほとんど流通していないため、xlarge設定が必要になる機会はあまりありません。

幅ではなく短辺を使用するのは、単純に幅800dp(w800dp)で判定した場合では縦持ちしたタブレットが含まれず、幅600dp(w600dp)では横向きのスマートフォン端末が含まれる可能性があるためです。
逆に、タブレット、スマートフォンにこだわらずにマルチペインを実装する場合は幅600dp(w600dp)あれば十分という考え方もできます。
レイアウトにあわせた適切な代替リソースの提供を行いましょう。

## レイアウトの決め方
まず、タブレット=横画面という思い込みを一旦捨てましょう。
7インチタブレットの短辺`600dp`でもスマートフォンと比べるとかなり広いので、タブレットの場合は縦持ちであってもマルチペンレイアウトなどのタブレット向けUIを実装しているアプリは多いです。
また、短辺/長辺でdpの範囲が区別されているとはいえ、10インチタブレット短辺の`800dp`は7インチタブレットの長辺`960dp`とそこまで大きくかわりません。

おすすめのレイアウト方法のひとつは、すべてのAndroid端末を横幅320dp(または300dp)ごとに分類する方法です。

```res/values/values.xml
<integer name="columns">1</integer>
```

```res/values-w600dp/values.xml
<integer name="columns">2</integer>
```

```res/values-w960dp/values.xml
<integer name="columns">3</integer>
```

```res/values-w1280dp/values.xml
<integer name="columns">4</integer>
```


上記の様に定義したintegerリソースを`RecyclerView`や`GridView`の列数として指定することで、スマートフォン縦向きでは1列、タブレット縦向きでは2列、小型タブレット横向きでは3列、より大きいタブレットでは4列…というように端末の画面領域に応じてコンテンツの密度を調整できます。
(本来はw640dpで区別するのが良さそうですが、小型タブレットでの利便性を考慮してw600dpで設定しています)

もちろんこのようなレイアウトはすべてのアプリに使えるものではないため、特に縦方向のサイズが重要なレイアウトでは`port`や`land`を使って端末の向き毎に代替リソースを提供したほうが良い場合もあります。
その場合は`sw600dp-land`や`sw960dp-land`のように短辺と組み合わせて判定することでより厳密になります。

## よくある工夫
タブレットにおけるレイアウトのパターンについてはまとめました(古くなっています)。
[Androidアプリのタブレット対応レイアウトまとめ](http://qiita.com/nein37/items/cc4bee6d76efef34f0ed)