［Android］画像をとりあえず Drawable フォルダに放り込むのをやめよう

# はじめに

Android ではデバイスの表示画面に応じた、ビットマップ画像を格納する仕組みが用意されています。
それがぞくに言う Drawable フォルダというやつで、DPI に応じて画像を格納できるフォルダが用意されています。

|密度修飾子 | Drawable フォルダ | DPI値 | ピクセル比 | スケーリング比 | 説明 | 
| --- | --- | --- | --- | --- | --- | --- |
| LDPI | drawable-ldpi | 〜120 | 36x36 | 0.75 | 低密度(ldpi)の画面(〜120dpi)に適応するリソース | 
| MDPI | drawable | 〜160 | 48x48 | 1.00 | 
| MDPI | drawable-mdpi | 〜160 | 48x48 | 1.00 | 中密度(mdpi)の画面(〜160dpi)に適応するリソース | 
| HDPI | drawable-hdpi | 〜240 | 72x72 | 1.50 | 高密度(hdpi)の画面(〜240dpi)に適応するリソース | 
| XHDPI | drawable-xhdpi | 〜320 | 96x96 | 2.00 | 超高密度(xhdpi)の画面(〜320dpi)に適応するリソース | 
| XXHDPI | drawable-xxhdpi | 〜480 | 144x144 | 3.00 | 超超高密度(xxhdpi)の画面(〜480dpi)に適応するリソース | 
| XXXHDPI | drawable-xxxhdpi | 〜640 | 192x192 | 4.00 | 超超超高密度(xxxhdpi)の画面(〜640dpi)に適応するリソース | 
| NODPI | drawable-nodpi | - | - | - | すべての密度に適用するリソース | - |


今までなるほど〜という感じで、個人開発では適当な Drawable に画像を放り込んでいました。
そのような感じで適当な運用をしていたらレイアウトが崩れなどの問題が起きたので、
ここらで Drawable の仕組みについて調べてまとめてみたいと思います。

# 画面の DPI に応じたリソースに切り替えてくれる 

Drawable フォルダは画面の DPI ごとにフォルダが区切られており、
Drawable フォルダに各画面のビットマップを格納しておけば、
画面の DPI に応じたリソースに自動で切り替えてくれるようになっています。

## 準備

### 表示するリソースを準備する

次の手順で 各 Drawable フォルダに画像を格納して、DPI の違う画面ごとにリソースを準備します。
今回は表示が切り替わっているが変わりやすいように、画像にピクセル比を載せたものを用意します。

1. `res` フォルダをエクスプローラなどで開く
2. `Drawable` フォルダを作成し、`dot.png`という名称で格納する。 

| Drawable フォルダ | ピクセル | 画像 |
| -------- | --- | -------- |
| drawable-ldpi | 36x36 | ![36 x 36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/207384fe-63f3-6b07-33a7-1acbf2fe9b2c.png) | 
| drawable-mdpi | 48x48 | ![48 x 48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/25c819fa-7e68-608a-1837-79805db9a355.png) |
| drawable-hdpi | 72x72 | ![72 x 72.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/c8a2029d-b26c-b77d-6a86-0aa6115f7d00.png) | 
| drawable-xhdpi | 96x96 | ![96 x 96.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/9825e720-c892-f752-9af2-2947ceff925e.png) |
| drawable-xxhdpi| 144x144 | ![144 x 144.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/41229c20-249a-fb10-795a-767c621e08b1.png) | 
| drawable-xxxhdpi | 192x192 | ![192x192.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/1eae5f41-8aa5-3260-2322-b7fbd2fe7fd7.png) |

### 画像を表示するアプリを作成する

次の手順で Drawable フォルダに格納した画像である、
`dot.png` ファイルを表示するアプリケーションを作成します。

1. `activity_main.xml`を開く
2. 画面中央に `ImageView` を配置し、`src`に`@drawable/dot`を指定する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/b47efb70-31ef-f847-a807-47bc7bf90a22.png)


```activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_gravity="center"
        android:src="@drawable/dot"/>

</FrameLayout>
```

## 実践

準備が整ったので、DPI が異なるデバイスでアプリを起動して動作を確認してみます。

### LDPI
DPI が LDPI であるデバイスでアプリを起動してみます。
意図したとおり drwable-ldpi フォルダに格納した画像が表示されます。

![Screenshot_1575987932.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/389cff39-19f7-e267-cab3-58721a24f597.png)


### MDPI
DPI が MDPI であるデバイスでアプリを起動してみます。
意図したとおり drwable-mdpi フォルダに格納した画像が表示されます。

![Screenshot_1575988096.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/211ee5bd-d10f-476f-6031-f03efc274afb.png)

### HDPI
DPI が HDPI であるデバイスでアプリを起動してみます。
意図したとおり drwable-hdpi フォルダに格納した画像が表示されます。

![Screenshot_1575988245.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/b36e7aa8-4d7a-fe58-9025-a682a0ca674a.png)


### XHDPI
DPI が XHDPI であるデバイスでアプリを起動してみます。
意図したとおり drwable-xhdpi フォルダに格納した画像が表示されます。

![Screenshot_1575988351.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/ab2d7a45-3b08-8064-34d5-19c1ee1888a5.png)


### XXHDPI
DPI が XXHDPI であるデバイスでアプリを起動してみます。
意図したとおり drwable-xxhdpi フォルダに格納した画像が表示されます。

![Screenshot_1575988462.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/b7751cdc-99e1-1630-a7af-bfc572babbf3.png)


### XXXHDPI

DPI が XXXHDPI であるデバイスでアプリを起動してみます。
意図したとおり drwable-xxxhdpi フォルダに格納した画像が表示されます。

![Screenshot_1575931567.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/95b7079d-6657-b0d2-a5f7-daa9ffcea52d.png)

というような感じで、 それぞれの DPI の Drawable フォルダに画像を
入れておくと Android が DPI にあわせて画像を切替えてくれます。
（DPI を指定せずに drawable に画像を格納する場合には mdpi の画像として扱われます）

このように Android では DPI ごとに用意した画像を表示する仕組みが整っており、
正しく利用すれば最適な品質で画像を表示することができるようになっています。


# デバイスの DPI にあったリソースがない場合、リスケールされる

それじゃ全部の Drawable フォルダに画像を入れて、
おかなきゃいけないのかというとそうでもないみたいです。

デバイスの DPI にあった Drawable フォルダに画像がない場合は、
別の DPI の Drawable フォルダに格納されている画像を選択するそうです。
しかもこの場合は リソースを表示する際にデバイスのDPIにあったサイズにリスケールしてくれます。

## 準備

### 表示するリソースを準備する

次の手順で drawable-xxxhdpi にのみ画像を格納します。
画像は先程のものと同じものを利用します。

1. `res` フォルダをエクスプローラなどで開く
2. `Drawable` フォルダを作成し、`dot.png`という名称で格納する。 

| Drawable フォルダ | ピクセル | 画像 |
| -------- | --- | -------- |
| drawable-xxxhdpi | 192x192 | ![192x192.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/1eae5f41-8aa5-3260-2322-b7fbd2fe7fd7.png) |

### 画像を表示するアプリを作成する

先程と同じアプリを利用します。

## 実践

準備が整いましたので DPI が違うデバイスでどのように表示が変わるか確認していきます。

### LDPI 

DPI が LDPI であるデバイスでアプリを起動してみます。
XXXHDPI に格納した画像が LDPI にあわせてリスケールされて小さくなってます。

![Screenshot_1575988909.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/1b4de52f-0ce9-e317-e3f4-140e69463cb8.png)

### HDPI

DPI が HDPI であるデバイスでアプリを起動してみます。
XXXHDPI に格納した画像が HDPI にあわせてリスケールされて小さくなってます。

![Screenshot_1575988805.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/4739575b-9c2a-8f60-54d6-5b05840fc0f9.png)

というような感じで 他の Drawable フォルダに画像がある場合は、
それを選択しまた DPI に合わせてリスケールしてくれます。

このようにリスケールをしてくれるので雑に Drawable フォルダを
管理しててもおおよそは問題なく画像を表示してくれるはずです。
ですが画像が意図しない大きさで表示されることにつながるので注意が必要です。

# 全ての DPI で同じリソースを使うには nodpi に入れる

全ての DPI で同じ Drawable 画像を使いたいんだけどってときもあると思います。
そういうときは drwable-nodpi に画像を入れることで全ての DPI で同じサイズの画像を利用できます。

## 準備

### 表示するリソースを準備する

次の手順で drawable-nohdpi にのみ画像を格納します。
画像は先程のものと同じものを利用します。

1. `res` フォルダをエクスプローラなどで開く
2. `Drawable` フォルダを作成し、`dot.png`という名称で格納する。 

| Drawable フォルダ | ピクセル | 画像 |
| -------- | --- | -------- |
| drawable-nohdpi | 1000x1000 | ![dot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/57d9035a-5fad-ec78-2c75-b2bfb77f688a.png) |

### 画像を表示するアプリを作成する

先程と同じアプリを利用します。

## 実践

準備が整いましたので DPI が違うデバイスでどのように表示が変わるか確認していきます。

### LDPI 

DPI が LDPI であるデバイスでアプリを起動してみます。
意図したとおりリスケールされていない画像が表示されています。

![Screenshot_1575989879.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/3bbd8e14-4bc0-d993-31ea-36389e1a7757.png)

### HDPI

DPI が HDPI であるデバイスでアプリを起動してみます。
意図したとおりリスケールされていない画像が表示されています。

![Screenshot_1575990184.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/07cb7613-c7bf-5fab-3020-31856af7f076.png)

という感じで DPI が異なるデバイスでもリスケールせずに同じ画像を表示できます。
なので特別な理由で同じ画像を使いたいときは drawable-nodpi が使えます。

# おわりに

次のような特徴を理解して、とりあえず Drawable フォルダに画像を放り込むのはやめましょう。

- DPI ごとに用意される Drawable フォルダに画像を格納することで、<br>
 画面の大きさごとに最適化された画像を表示することができる。
- DPI を指定せずに drawable に画像を格納すると mdpi の画像として取り扱われる。
- デバイスの DPI にあった Drawable フォルダに画像がない場合は、<br>
  別の Drawable フォルダの画像が適応される。<br>
  その際には画像はリスケールされるので注意が必要です。
- 全 DPI で同じリソースを利用した場合には drawable-nodpi に画像を格納すればよい。

# 参考文献

- [各種のピクセル密度をサポートする | Android Developers](https://developer.android.com/docs?hl=ja)