各種のピクセル密度をサポートする

Android デバイスは、スマートフォン、タブレット、テレビなどの画面サイズが異なるだけでなく、画面のピクセルサイズもさまざまです。1 インチあたり 160 ピクセルのデバイスもあれば、480 ピクセルのデバイスもあります。こうしたピクセル密度の違いを考慮しないと、システムによって画像が拡大縮小されて画像が不鮮明になったり、誤ったサイズで表示されたりする可能性があります。

このページでは、解像度に依存しない測定単位を使用し、ピクセル密度ごとに代替ビットマップ リソースを提供することで、さまざまなピクセル密度をサポートするようにアプリを設計する方法について説明します。

これらの手法の概要については、次の動画をご覧ください。

アイコン アセットの設計について詳しくは、マテリアル デザインのアイコン ガイドラインをご覧ください。

密度非依存ピクセルを使用する

距離やサイズの定義にピクセルを使用しないでください。画面によってピクセル密度が異なるため、同じピクセル数でもデバイスが異なる物理サイズに対応するため、ピクセル数で寸法を定義するのは難しい問題です。

密度が異なる 2 つのデバイス ディスプレイの例を示す画像
図 1: 同じサイズの 2 つの画面でピクセル数が異なる場合があります。

密度が異なる画面でも UI の表示サイズを維持するには、測定単位に密度非依存ピクセル(dp)を使用して UI を設計します。1 dp は、中密度画面(160 dpi、つまり「ベースライン」密度)の 1 ピクセルにほぼ等しい仮想ピクセル単位です。Android では、この値が他の密度ごとの適切な実際のピクセル数に変換されます。

図 1 の 2 つのデバイスについて考えてみましょう。幅 100 ピクセルのビューは、左側のデバイスでははるかに大きく表示されます。幅 100 dp に定義されたビューは、どちらの画面でも同じサイズで表示されます。

テキストサイズを定義する場合は、代わりに拡張可能ピクセル(sp)を単位として使用できます。sp 単位は、デフォルトでは dp と同じサイズですが、ユーザーが選択したテキストサイズに基づいてサイズ変更されます。レイアウト サイズに sp を使用しないでください。

たとえば、2 つのビューの間隔を指定するには、dp を使用します。

<Button android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/clickme"
    android:layout_marginTop="20dp" />

テキストサイズを指定する場合は、sp を使用します。

<TextView android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="20sp" />

dp 単位をピクセル単位に変換する

場合によっては、寸法を dp で表してから、それをピクセルに変換する必要があります。dp 単位から画面ピクセルへの変換は次のとおりです。

px = dp * (dpi / 160)

注: ピクセルの計算でこの方程式をハードコードしないでください。代わりに、TypedValue.applyDimension() を使用してください。これにより、さまざまなディメンション(dp、sp など)がピクセルに変換されます。

ユーザーが指を 16 ピクセル以上動かすと、スクロール操作またはフリング操作が認識されるアプリについて考えてみましょう。ベースライン画面では、ユーザーの指が 16 pixels / 160 dpi を動かす必要があります。これは 1/10 インチ(2.5 mm)に相当し、操作が認識されるまでの時間です。

高密度ディスプレイ(240 dpi)を搭載したデバイスでは、ユーザーの指で 16 pixels / 240 dpi を動かす必要があります。これは 1 インチの 1/15(1.7 mm)に等しくなります。距離が大幅に短くなり、アプリがユーザーに反応するように見えます。

この問題を解決するには、ジェスチャーのしきい値をコードで dp で表してから、実際のピクセルに変換します。次に例を示します。

Kotlin

// The gesture threshold expressed in dp
private const val GESTURE_THRESHOLD_DP = 16.0f

private var gestureThreshold: Int = 0

// Convert the dps to pixels, based on density scale
gestureThreshold = TypedValue.applyDimension(
  COMPLEX_UNIT_DIP,
  GESTURE_THRESHOLD_DP + 0.5f,
  resources.displayMetrics).toInt()

// Use gestureThreshold as a distance in pixels...

Java

// The gesture threshold expressed in dp
private final float GESTURE_THRESHOLD_DP = 16.0f;

// Convert the dps to pixels, based on density scale
int gestureThreshold = (int) TypedValue.applyDimension(
  COMPLEX_UNIT_DIP,
  GESTURE_THRESHOLD_DP + 0.5f,
  getResources().getDisplayMetrics());

// Use gestureThreshold as a distance in pixels...

DisplayMetrics.density フィールドは、現在のピクセル密度に応じて dp 単位をピクセルに変換するために使用されるスケール ファクタを指定します。DisplayMetrics.density は、中密度画面では 1.0、高密度画面では 1.5 となります。これは、超高密度画面では 2.0、低密度画面では 0.75 になります。この数値は、現在の画面の実際のピクセル数を取得するために TypedValue.applyDimension() によって使用されます。

事前スケーリングされた構成値を使用する

ViewConfiguration クラスを使用すると、Android システムで使用される一般的な距離、速度、時間にアクセスできます。たとえば、フレームワークでスクロールのしきい値として使用される距離(ピクセル単位)は getScaledTouchSlop() で取得できます。

Kotlin

private val GESTURE_THRESHOLD_DP = ViewConfiguration.get(myContext).scaledTouchSlop

Java

private final int GESTURE_THRESHOLD_DP = ViewConfiguration.get(myContext).getScaledTouchSlop();

getScaled 接頭辞で始まる ViewConfiguration のメソッドは、現在のピクセル密度に関係なく適切に表示されるピクセル単位の値を返します。

ベクター グラフィックを優先

密度別のバージョンの画像を複数作成するのではなく、ベクター グラフィックを 1 つだけ作成することもできます。ベクター グラフィックでは、ピクセル ビットマップではなく、XML を使用してパスと色を定義して画像を作成します。そのため、ベクター グラフィックは、アーティファクトを拡大することなく任意のサイズにスケーリングできますが、通常は写真ではなくアイコンなどのイラストに適しています。

多くの場合、ベクター グラフィックは SVG(Scalable Vector Graphics)ファイルとして提供されますが、Android はこの形式をサポートしていないため、SVG ファイルを Android のベクター型ドローアブル形式に変換する必要があります。

Android Studio の Vector Asset Studio を使用して、SVG をベクター型ドローアブルに変換する手順は次のとおりです。

  1. [Project] ウィンドウで res ディレクトリを右クリックし、[New] > [Vector Asset] を選択します。
  2. [Local file (SVG, PSD)] を選択します。
  3. インポートするファイルを見つけて、調整します。

    Android Studio で SVG をインポートする方法を示す画像
    図 2: Android Studio で SVG をインポートする方法。

    [Asset Studio] ウィンドウに、ベクター型ドローアブルがファイルの一部のプロパティをサポートしていないことを示すエラーが表示される場合があります。これにより、ファイルをインポートできなくなることはありません。サポートされていないプロパティは無視されます。

  4. [Next] をクリックします。

  5. 次の画面で、プロジェクト内のファイルが必要なソースセットを確認し、[Finish] をクリックします。

    1 つのベクター型ドローアブルはすべてのピクセル密度で使用できるため、このファイルは次の階層に示すように、デフォルトのドローアブル ディレクトリに配置されます。密度固有のディレクトリを使用する必要はありません。

    res/
      drawable/
        ic_android_launcher.xml
    

ベクター グラフィックの作成について詳しくは、ベクター型ドローアブルのドキュメントをご覧ください。

代替ビットマップを提供する

ピクセル密度が異なるデバイスで優れたグラフィック品質を実現するには、各ビットマップの複数のバージョンを密度バケットごとに 1 つずつ、対応する解像度で提供します。そうしないと、Android はビットマップを各画面で同じ表示スペースを占めるようにスケーリングする必要があります。その結果、ぼかしなどのスケーリング アーティファクトが生じます。

さまざまな密度サイズのビットマップの相対サイズを示す画像
図 3: 各種の密度バケットでのビットマップの相対サイズ。

アプリで使用できる密度バケットはいくつかあります。表 1 に、使用可能なさまざまな構成修飾子と、それらが適用される画面タイプを示します。

表 1. さまざまなピクセル密度の構成修飾子。

密度修飾子 説明
ldpi 低密度(ldpi)画面(~ 120 dpi)用のリソース。
mdpi 中密度(mdpi)画面(~ 160 dpi)用のリソース。これはベースライン密度です。
hdpi 高密度(hdpi)画面(~ 240 dpi)用のリソース。
xhdpi 超高密度(xhdpi)画面(~ 320 dpi)用のリソース。
xxhdpi 超超高密度(xxhdpi)画面(~ 480 dpi)用のリソース。
xxxhdpi 超超超高密度(xxxhdpi)の使用(~ 640 dpi)向けのリソース。
nodpi すべての密度に適用するリソース(密度非依存リソース)。現在の画面密度に関係なく、この修飾子でタグ付けされたリソースはスケーリングされません。
tvdpi mdpi から hdpi の範囲の画面(およそ 213 dpi)のリソース。これは「メイン」の密度グループとしては認識されません。主にテレビが念頭に置かれており、ほとんどのアプリでは必要ありません。ほとんどのアプリには mdpi と hdpi のリソースを用意すれば十分であり、システムによって適宜拡大縮小されます。tvdpi リソースを指定する必要がある場合は、1.33 × mdpi の係数でサイズを調整します。たとえば、mdpi 画面で 100x100 ピクセルの画像は、tvdpi では 133x133 ピクセルです。

各種の密度用に代替ビットマップ ドローアブルを作成するには、6 つの主要な密度間のスケーリング比 3:4:6:8:12:16 に従います。たとえば、中密度画面用に 48x48 ピクセルのビットマップ ドローアブルがある場合、サイズは次のようになります。

  • 低密度(ldpi)の場合は 36x36(0.75x)
  • 中密度(mdpi): 48x48(1.0x ベースライン)
  • 高密度(hdpi)の場合は 72x72(1.5 倍)
  • 超高密度(xhdpi)の場合は 96x96(2.0x)
  • 超超高密度(xxhdpi)には 144x144(3.0x)
  • 超超超高密度(xxxhdpi)は 192x192(4.0x)

生成された画像ファイルを res/ の適切なサブディレクトリに配置します。

res/
  drawable-xxxhdpi/
    awesome_image.png
  drawable-xxhdpi/
    awesome_image.png
  drawable-xhdpi/
    awesome_image.png
  drawable-hdpi/
    awesome_image.png
  drawable-mdpi/
    awesome_image.png

@drawable/awesomeimage を参照するたびに、画面の dpi に基づいて適切なビットマップが選択されます。その密度に対応する密度固有のリソースを指定しない場合、システムは次に最適な密度を見つけて、画面に合わせて調整します。

ヒント: 実行時に自分で画像の調整を行う場合など、システムによるスケーリングを希望しないドローアブル リソースがある場合は、nodpi 構成修飾子が付いたディレクトリにそれらのリソースを配置します。 この修飾子が割り当てられたリソースは密度非依存と見なされ、スケーリングは行われません。

その他の構成修飾子と、Android が現在の画面構成に適したリソースを選択する仕組みについて詳しくは、アプリリソースの概要をご覧ください。

アプリアイコンを mipmap ディレクトリに配置する

他のビットマップ アセットと同様に、アプリアイコンの密度別バージョンを用意する必要があります。ただし、アプリ ランチャーによっては、デバイスの密度バケットで要求されるものよりも 25% 大きくアプリアイコンが大きくなることがあります。

たとえば、デバイスの密度バケットが xxhdpi で、指定した最大のアプリアイコンが drawable-xxhdpi にある場合、アプリ ランチャーはこのアイコンを拡大して、不鮮明に見えます。

これを回避するには、すべてのアプリアイコンを drawable ディレクトリではなく、mipmap ディレクトリに配置します。drawable ディレクトリとは異なり、密度固有の APK をビルドする場合でも、すべての mipmap ディレクトリは APK に保持されます。これにより、ランチャー アプリは最適な解像度のアイコンを選択してホーム画面に表示できます。

res/
  mipmap-xxxhdpi/
    launcher_icon.png
  mipmap-xxhdpi/
    launcher_icon.png
  mipmap-xhdpi/
    launcher_icon.png
  mipmap-hdpi/
    launcher_icon.png
  mipmap-mdpi/
    launcher_icon.png

前述の xxhdpi デバイスの例では、mipmap-xxxhdpi ディレクトリに高密度ランチャー アイコンを配置できます。

アイコンの設計ガイドラインについては、システム アイコンをご覧ください。

アプリアイコンの作成方法については、Image Asset Studio を使用してアプリアイコンを作成するをご覧ください。

一般的には見られない密度の問題に関するアドバイス

このセクションでは、Android がさまざまなピクセル密度でビットマップのスケーリングを実行する方法と、さまざまな密度でのビットマップの描画方法を詳細に制御する方法について説明します。アプリがグラフィックを操作する場合、またはさまざまなピクセル密度で実行する際に問題が発生した場合は、このセクションは無視してください。

実行時にグラフィックを操作する際に複数の密度をサポートする方法を理解するには、システムがビットマップの適切なスケーリングをどのように保証しているかを理解する必要があります。これは、次の方法で行われます。

  1. ビットマップ ドローアブルなどのリソースの事前スケーリング

    システムは、現在の画面の密度に基づいて、アプリの密度固有のリソースを使用します。適切な密度でリソースを使用できない場合は、デフォルトのリソースを読み込み、必要に応じてスケールアップまたはスケールダウンします。システムは、デフォルト リソース(構成修飾子のないディレクトリにあるリソース)がベースライン ピクセル密度(mdpi)用に設計されていると想定し、そのビットマップを現在のピクセル密度に適したサイズにサイズ変更します。

    事前にスケーリングされたリソースの寸法をリクエストすると、スケーリング後の寸法を表す値が返されます。たとえば、mdpi 画面用に 50x50 ピクセルで設計されたビットマップは、hdpi 画面では 75x75 ピクセルにスケーリングされます(hdpi に代替リソースがない場合)。この場合、サイズはその旨をシステムから報告します。

    状況によっては、Android でリソースを事前にスケーリングしたくない場合があります。事前スケーリングを回避する最も簡単な方法は、nodpi 構成修飾子を付けたリソース ディレクトリにリソースを配置することです。次に例を示します。

    res/drawable-nodpi/icon.png

    システムがこのフォルダの icon.png ビットマップを使用する場合、現在のデバイス密度に基づいてスケーリングは行われません。

  2. ピクセルの寸法と座標の自動スケーリング

    ディメンションと画像の事前スケーリングを無効にするには、マニフェストで android:anyDensity"false" に設定するか、Bitmap に対してプログラムで inScaled"false" に設定します。この場合、システムは描画時に絶対ピクセル座標とピクセル寸法値を自動的にスケーリングします。これは、ピクセルで定義された画面要素が、ベースライン ピクセル密度(mdpi)で表示できるのとほぼ同じ物理サイズで引き続き表示されるようにするためです。このスケーリングはアプリに対して透過的に処理され、スケーリングされたピクセル寸法ではなく物理ピクセルの寸法ではなく、スケーリングされたピクセルの寸法がアプリに報告されます。

    たとえば、デバイスが従来の HVGA 画面とほぼ同じサイズの 480x800 の WVGA 高密度画面を搭載しているが、事前スケーリングを無効にしたアプリを実行しているとします。この場合、システムは画面ディメンションをクエリし、320x533(ピクセル密度のおおよその mdpi 変換)をレポートすると、アプリに「嘘」をつけます。

    次に、アプリが(10,10)から(100, 100)までの長方形を無効にするなどの描画オペレーションを行うと、システムは座標を適切な量にスケーリングして変換し、実際に領域(15,15)から(150, 150)を無効にします。アプリがスケーリングされたビットマップを直接操作する場合、この不一致により予期しない動作が発生する可能性がありますが、アプリのパフォーマンスを可能な限り最善のものにするうえで、妥当なトレードオフであると考えられます。このような場合は、dp 単位をピクセル単位に変換するをご覧ください。

    通常、事前スケーリングは無効にしません。複数の画面をサポートするには、このページで説明する基本的な手法に従うことをおすすめします。

アプリがビットマップを操作する場合や、他のなんらかの方法で画面上のピクセルを直接操作する場合は、さまざまなピクセル密度をサポートするために、追加の手順が必要になることがあります。たとえば、指が触れるピクセル数を数えることでタッチ操作に応答する場合は、実際のピクセルではなく適切な密度非依存ピクセルの値を使用する必要がありますが、dp 値と px 値の変換は可能です。

すべてのピクセル密度でテストする

UI が正しくスケーリングされるように、ピクセル密度が異なる複数のデバイスでアプリをテストします。可能であれば実機でテストします。各種ピクセル密度のすべてに対応する実機が用意されていない場合は、Android Emulator を使用します。

実機でテストしたいがデバイスを購入しない場合は、Firebase Test Lab を使用して Google データセンター内のデバイスにアクセスできます。