けんごのお屋敷

2014-10-18

謎の単位 "dp" (Density-independent Pixel) にせまる。あと drawable の話とか in Android

Android 端末のディスプレイってめちゃくちゃいろんなサイズがあって Android を始めたばかりの自分にはそれぞれに最適なレイアウトや画像をどうやって作ればいいのかって全然わかっていませんでした。それに Android ではサイズの単位に px (pixel) じゃなくて dp (density-independent pixel) を使うらしいってことは聞いたことあるけど、そもそも dp ってなんなの?という感じだし。わからないまま放置しておくのも良くないので色々調べてみたらだんだんとわかってきたので、忘れない内にまとめておこうかと思う。既にネットにある記事の二番煎じになるかもしれないけど、インプットしたものを体系的にまとめて知識を自分のものにするためにも再まとめ的なものを書く。

用語定義

自分がこの話題について調べ始めた時、自分の中で Android 用の各用語の定義がはっきりしておらず意味もあまりわかってなかったので、いろんなサイトを見ても全然理解が進みませんでした。この辺の用語の意味がわかってきてからなんとなく理解が進み始めたので、まずは各用語がどういう意味なのかをはっきりさせておきます。

解像度

画面のピクセル数のことを表します。例えば 480 x 800 とか 720 x 1280 とかいうやつですね。解像度の単位は px (pixel) です。いくつかの実際の機種の解像度をみてみると

デバイス名 解像度 備考
GALAXY S4 SC-04E 1080px x 1920px
Xperia Tablet Z SO-03E 1200px x 1920px タブレット
GALAXY S2 SC-02C 480px x 800px

てな感じで、各機種毎に解像度は違います。ただ、解像度は実際の画面の大きさが何インチあるかとは無関係にディスプレイのピクセル数を表していますので、単純に解像度が大きければ画面サイズも大きくなるのかというと、そうではありません。スマホよりタブレットの方が物理的に大きいのは直感的にわかると思いますが、GALAXY S4 より Xperia Tablet Z の解像度の方が劇的に大きいかというとそうでもなく、横幅が少し広いくらいの解像度になっています。

画面サイズ

では、実際の画面の物理的なサイズはというと画面サイズで表されます。ディスプレイサイズとも言いますね。単位はインチ (inch) です。画面のサイズを表す時は画面の対角線上の長さを表記します。つまり画面サイズ 4 インチという表記の場合、縦や横の長さが 4 インチではなく、画面の左上から右下までの対角線の長さが 4 インチということです。ちなみに 1 インチは 2.54 cm。

解像度と同じように各機種の画面サイズをみてみると

デバイス名 解像度 画面サイズ
GALAXY S4 SC-04E 1080px x 1920px 5.0インチ
Xperia Tablet Z SO-03E 1200px x 1920px 10.1インチ
GALAXY S2 SC-02C 480px x 800px 4.3 インチ

タブレットはやっぱり大きいですね。GALAXY S4 と Xperia Tablet Z は解像度はほぼ同じなのですが画面サイズが 2 倍違いますし、GALAXY S4 と GALAXY S2 を見てみると画面サイズはあまり変わらないけど解像度は 2 倍近く違います。ここが次の画面密度の話につながっていきます。

ピクセル密度

さて、解像度と画面サイズが比例しないのは、このピクセル密度というものがあるからです。画面密度と言う場合もあるようです。

ピクセル密度は 1 インチの中にいくつのピクセルが詰まっているかを表します。単位は dpi (dot per inch) です。この値が大きければ大きいほど綺麗な画面になります。各端末のピクセル密度を見てみるとこのようになっています。

デバイス名 解像度 画面サイズ ピクセル密度
GALAXY S4 SC-04E 1080px x 1920px 5.0インチ 約440dpi
Xperia Tablet Z SO-03E 1200px x 1920px 10.1インチ 約220dpi
GALAXY S2 SC-02C 480px x 800px 4.3 インチ 約210dpi

要するに GALAXY S4 は 1 インチの中に約 440 個のピクセルが詰まってますよ、Xperia Tablet Z は約 220 個のピクセルが詰まってるし GALAXY S2 は約 210 個のピクセルが詰まってますよ、ということです。当然 1 インチの中にたくさんのピクセルが詰まってる方が、ピクセル 1 つ 1 つのサイズは小さくなって、その分たくさんの表現が可能になって画面が綺麗になるということです。

GALAXY S4 と Xperia Tablet Z のピクセル密度を比べると 2 倍違います。また GALAXY S4 と GALAXY S2 を比べてみても約 2 倍違います。これは各端末の解像度と画面サイズの大きさを比べて少し考えてみれば、中に詰まってるピクセル数も変わってくるのは自明なことではないでしょうか。ちなみに勘の良い人はわかるかもしれませんが、解像度と画面サイズがわかってればそこからピクセル密度を計算できます。

ピクセル密度グループ

ピクセル密度グループという名前か正しいかどうかはわかりませんが、ピクセル密度はその数値によってグループ分けされています。具体的には

グループ名称 ピクセル密度の範囲
ldpi (low) ~ 120dpi
mdpi (medium) ~ 160dpi
hdpi (high) ~ 240dpi
xhdpi (extra-high) ~ 320dpi
xxhdpi (extra-extra-high) ~ 480dpi
xxxhdpi (extra-extra-extra-high) ~ 640dpi

このようにピクセル密度によって名前がついています。この名前、Android 開発の際にもちらっと出てくるので覚えておくと吉です。各端末がどのグループに属するのかを照らしあわせてみると

デバイス名 解像度 画面サイズ ピクセル密度 ピクセル密度グループ
GALAXY S4 SC-04E 1080px x 1920px 5.0インチ 約440dpi xxhdpi
Xperia Tablet Z SO-03E 1200px x 1920px 10.1インチ 約220dpi hdpi
GALAXY S2 SC-02C 480px x 800px 4.3 インチ 約210dpi hdpi

こんな感じですね。

密度非依存ピクセル

やっとここから dp の話です。

dp は冒頭にもあるように Density-independent Pixel の略で、日本語だと密度非依存ピクセル。これまで説明してきたように Android にはいろんなピクセル密度を持つ数多くの端末があります。これはつまり各端末によって 1 ピクセルの大きさが違うということです。どういうことかイメージするために画像をまじえて説明してみます。

ピクセル指定

まず最初に px で指定した場合のことを考えてみます。

この画像の格子 1 つ 1 つをピクセルと考えます。左側がより高密度、右側がより低密度なディスプレイです。このそれぞれのディスプレイの真ん中辺りに 1 ピクセルの赤い点を描画するとします。するとそれぞれ以下のようになるのはすぐに想像できるでしょう。

密度によって 1 ピクセルの大きさが違うので、高密度なディスプレイの方が低密度なディスプレイより小さくなってます。つまり同じ 1 ピクセルでも、ディスプレイによっては表示される時の大きさが違います。通常、単位をピクセルで指定すると先に見たとおり、ディスプレイによって(つまり端末によって)表示される大きさが異なります。Android 開発者達はそれでは困るため、密度に依存しない単位が必要になります。これが密度非依存ピクセルです。

密度非依存ピクセル指定

では dp で指定するとどうなるのでしょうか。dp は mdpi を 1px (ベースライン) として、それぞれのピクセル密度グループで拡大・縮小を行います。表を見たほうがわかりやすいと思います。

ldpi mdpi hdpi xhdpi xxhdpi xxxhdpi
0.75倍 1(基準) 1.5倍 2倍 3倍 4倍

たとえば 100dp を指定した場合

ldpi mdpi hdpi xhdpi xxhdpi xxxhdpi
75px 100px(基準) 150px 200px 300px 400px

という風に、端末の属するピクセル密度グループによって拡大・縮小をしたピクセルで表示してくれます。dp を指定することで各端末間でのピクセルの大きさの違いを吸収して、同じような大きさで表示することができるようになります。先ほどの真ん中に 1 ピクセルの赤い点を描画する例で dp を使った場合を例えるとこんな感じになります。

drawableリソース

Android には画像ファイルをおける res/drawable ディレクトリがあります。この中の画像達もピクセル密度グループによって自動的に拡大・縮小されることになります。ただし drawable ディレクトリの名前を適切につけることでピクセル密度グループ毎に画像を準備して読み込ませることができます。

画像を表示する端末の密度グループ ディレクトリ名
ldpi drawable-ldpi
mdpi drawable-mdpi
hdpi drawable-hdpi
xhdpi drawable-xhdpi
xxhdpi drawable-xxhdpi
xxxhdpi drawable-xxxhdpi

drawable の後ろにハイフンつきでピクセル密度グループの名前がついただけです。各ディレクトリにピクセル密度グループ毎のサイズの画像を用意しておけば、自動的に自分のピクセル密度に適した画像ファイルを読み込んでくれます。ただし、全てのピクセル密度グループ用に画像を用意しなければならないかというとそうではなく、端末が属するピクセル密度グループのディレクトリ内に画像ファイルがなければ自動的に他のディレクトリから画像を読み込んで、拡大・縮小をして表示されることになります。拡大・縮小の倍率は dp で説明した時の表と同じ比率になります。

具体例を考えてみる

とはいえよくわからないとは思うので具体例として 100px x 100px の画像を画面に表示することを考えてみます。

drawable ディレクトリに画像を用意した場合

res/drawable ディレクトリに画像を用意すると res/drawable-mdpi に画像を用意したのと同じことになります。要するに mdpi が基準になるので

画像を表示する端末の密度グループ 実際に表示される画像のサイズ
ldpi 75px x 75px
mdpi 100px x 100px (基準)
hdpi 150px x 150px
xhdpi 200px x 200px
xxhdpi 300px x 300px
xxxhdpi 400px x 400px

という風に各ピクセル密度グループ毎に拡大・縮小されます。

drawable-xhdpi ディレクトリに画像を用意した場合

この場合は xhdpi が基準になるので

画像を表示する端末の密度グループ 実際に表示される画像のサイズ
ldpi 37.5px x 37.5px
mdpi 50px x 50px
hdpi 75px x 75px
xhdpi 100px x 100px (基準)
xxhdpi 150px x 150px
xxxhdpi 200px x 200px

という風に各ピクセル密度グループ毎に拡大・縮小されます。ldpi で端数が出ています。たぶんこの場合はぼやけたりするんじゃないでしょうか。

実験

論より証拠。ということで、こんなアプリケーションを作ってエミュレーターで実行してみました。

ボタン

まずはボタンのサイズを px と dp で指定して比べてみます。

MainActivity.java

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
    }
}

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <Button
        android:text="Hello World"
        android:width="100dp"
        android:height="100dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <Button
        android:text="Hello World"
        android:width="100px"
        android:height="100px"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

結果

左から

  • GALAXY S4
  • Xperia Tablet Z
  • GALAXY S2

です。GALAXY S4 だけ画像が大きいのはピクセル密度が高いからです。

画像

Android Studio で新しくプロジェクトを作ると最初から drawable ディレクトリに用意されているあの ic_launcher.png の 144px の画像を使って実験してみます。

画像ファイルを以下のように配置してみます。

  • drawable-xxhdpi/a144_in_xxhdpi.png 中身は 144px の ic_launcher.png です
  • drawable-mdpi/a144_in_xxhdpi.png 中身は上と同じ 144px の ic_launcher.png です

どちらとも同じ画像ですが配置しているディレクトリが違います。これをそれぞれ ImageView を使って読み込んでみます。

MainActivity.java

ボタンの時と同じ

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ImageView
        android:contentDescription="@string/hello_world"
        android:src="@drawable/a144_in_xxhdpi"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <ImageView
        android:contentDescription="@string/hello_world"
        android:src="@drawable/a144_in_mdpi"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

結果

GALAXY S4 (xxhdpi) で表示しています。

上の方に表示されている画像は drawable-xxhdpi に入っているのでそのまま 144px で表示されていますが、下の方に表示されている画像は drawable-mdpi に入ってるので mdpi を基準として xxhdpi で表示されており、大きさが 3 倍になっています。

まとめ

まあまあ長文になってしまいました。Android 界隈に飛び込んで間もないので、もし何か間違っていたら指摘ください!

  • このエントリーをはてなブックマークに追加