ドローアブルの概要

Compose を試す
Jetpack Compose は Android の推奨 UI ツールキットです。Compose でグラフィックを表示する方法を学習します。

アプリに静止画像を表示する必要がある場合、Drawable クラスとそのサブクラスを使用して、図形や画像を描画できます。Drawable は、描画可能なものの一般的な抽象化です。各種のサブクラスは、特定の画像シナリオに役立つものであり、サブクラスを拡張して、独自の動作をする独自のドローアブル オブジェクトを定義できます。

クラス コンストラクタを使用する以外に、Drawable を定義しインスタンス化する方法には次の 2 つがあります。

  • プロジェクトに保存されている画像リソース(ビットマップ ファイル)をインフレートする。
  • ドローアブル プロパティを定義する XML リソースをインフレートする。

注: 代わりにベクター型ドローアブルを使用することをおすすめします。ベクター型ドローアブルを使用すると、一連の点、線、曲線と、関連する色情報からなる画像を定義します。これにより、品質を損なうことなく、ベクター型ドローアブルをさまざまなサイズにスケーリングできます。詳細については、ベクター型ドローアブルの概要をご覧ください。

リソースの画像からドローアブルを作成する

プロジェクト リソースから画像ファイルを参照することで、アプリにグラフィックを追加できます。サポートされているファイル形式は、PNG(推奨)、JPG(許容)、GIF(非推奨)です。アプリのアイコン、ロゴ、その他のグラフィック(ゲームで使用されるものなど)は、この手法に適しています。

画像リソースを使用するには、プロジェクトの res/drawable/ ディレクトリにファイルを追加します。プロジェクトでは、コードまたは XML レイアウトから画像リソースを参照できます。どちらの場合も、リソース ID(ファイル形式の拡張子のないファイル名)を使用して参照されます。たとえば、my_image.pngmy_image として参照します。

注: res/drawable/ ディレクトリに配置された画像リソースは、ビルドプロセス時に aapt ツールによって可逆画像圧縮で自動的に最適化される場合があります。たとえば、256 色以上を必要としないトゥルーカラーの PNG を、カラーパレットを使用する 8 ビットの PNG に変換できます。これにより、画質を変えずにメモリ使用量を削減できます。そのため、このディレクトリに配置された画像バイナリがビルド時に変更される可能性があります。ビットマップに変換するために画像をビットストリームとして読み取る場合は、代わりに画像を res/raw/ フォルダに保存してください。aapt ツールによって画像は変更されません。

次のコード スニペットは、ドローアブル リソースから作成された画像を使用し、レイアウトに追加する ImageView を作成する方法を示しています。

Kotlin

private lateinit var constraintLayout: ConstraintLayout

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // Instantiate an ImageView and define its properties
    val i = ImageView(this).apply {
        setImageResource(R.drawable.my_image)
        contentDescription = resources.getString(R.string.my_image_desc)

        // set the ImageView bounds to match the Drawable's dimensions
        adjustViewBounds = true
        layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT)
    }

    // Create a ConstraintLayout in which to add the ImageView
    constraintLayout = ConstraintLayout(this).apply {

        // Add the ImageView to the layout.
        addView(i)
    }

    // Set the layout as the content view.
    setContentView(constraintLayout)
}

Java

ConstraintLayout constraintLayout;

protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

  // Create a ConstraintLayout in which to add the ImageView
  constraintLayout = new ConstraintLayout(this);

  // Instantiate an ImageView and define its properties
  ImageView i = new ImageView(this);
  i.setImageResource(R.drawable.my_image);
  i.setContentDescription(getResources().getString(R.string.my_image_desc));

  // set the ImageView bounds to match the Drawable's dimensions
  i.setAdjustViewBounds(true);
  i.setLayoutParams(new ViewGroup.LayoutParams(
          ViewGroup.LayoutParams.WRAP_CONTENT,
          ViewGroup.LayoutParams.WRAP_CONTENT));

  // Add the ImageView to the layout and set the layout as the content view.
  constraintLayout.addView(i);
  setContentView(constraintLayout);
}

また、次の例に示すように、画像リソースを Drawable オブジェクトとして処理したい場合もあります。

Kotlin

val myImage: Drawable = ResourcesCompat.getDrawable(context.resources, R.drawable.my_image, null)

Java

Resources res = context.getResources();
Drawable myImage = ResourcesCompat.getDrawable(res, R.drawable.my_image, null);

警告: インスタンス化したオブジェクトの数に関係なく、プロジェクト内の一意の各リソースは 1 つの状態のみを維持できます。たとえば、同じ画像リソースから 2 つの Drawable オブジェクトをインスタンス化し、一方のオブジェクトのプロパティ(アルファなど)を変更すると、もう一方のオブジェクトにも影響します。画像リソースの複数のインスタンスを扱う場合は、Drawable オブジェクトを直接変換するのではなく、トゥイーン アニメーションを実行する必要があります。

次の XML スニペットは、XML レイアウトの ImageView にドローアブル リソースを追加する方法を示しています。

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

プロジェクト リソースの使用方法の詳細については、リソースとアセットをご覧ください。

注: ドローアブルのソースとして画像リソースを使用する場合は、さまざまなピクセル密度に適した画像のサイズにしてください。画像が不適切な場合は、収まるように拡大されるため、ドローアブルにアーティファクトが生じる可能性があります。詳しくは、各種のピクセル密度をサポートするをご覧ください。

XML リソースからドローアブルを作成する

作成する Drawable オブジェクトがあり、最初はコードやユーザー操作で定義された変数に依存しない場合は、XML で Drawable を定義することをおすすめします。ユーザーによるアプリの操作中に Drawable でプロパティが変更されることが想定される場合でも、オブジェクトをインスタンス化した後にプロパティを変更できるため、XML でオブジェクトを定義することを検討してください。

Drawable を XML で定義したら、プロジェクトの res/drawable/ ディレクトリにファイルを保存します。次の例は、Drawable を継承する TransitionDrawable リソースを定義する XML を示しています。

<!-- res/drawable/expand_collapse.xml -->
<transition xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/image_expand"/>
    <item android:drawable="@drawable/image_collapse"/>
</transition>

次に、Resources#getDrawable() を呼び出し、XML ファイルのリソース ID を渡して、オブジェクトを取得してインスタンス化します。inflate() メソッドをサポートするすべての Drawable サブクラスは、XML で定義して、アプリでインスタンス化できます。

XML インフレーションをサポートする各ドローアブル クラスは、オブジェクト プロパティの定義に役立つ特定の XML 属性を使用します。次のコードは、TransitionDrawable をインスタンス化し、ImageView オブジェクトのコンテンツとして設定します。

Kotlin

val transition= ResourcesCompat.getDrawable(
        context.resources,
        R.drawable.expand_collapse,
        null
) as TransitionDrawable

val image: ImageView = findViewById(R.id.toggle_image)
image.setImageDrawable(transition)

// Description of the initial state that the drawable represents.
image.contentDescription = resources.getString(R.string.collapsed)

// Then you can call the TransitionDrawable object's methods.
transition.startTransition(1000)

// After the transition is complete, change the image's content description
// to reflect the new state.

Java

Resources res = context.getResources();
TransitionDrawable transition =
    (TransitionDrawable) ResourcesCompat.getDrawable(res, R.drawable.expand_collapse, null);

ImageView image = (ImageView) findViewById(R.id.toggle_image);
image.setImageDrawable(transition);

// Description of the initial state that the drawable represents.
image.setContentDescription(getResources().getString(R.string.collapsed));

// Then you can call the TransitionDrawable object's methods.
transition.startTransition(1000);

// After the transition is complete, change the image's content description
// to reflect the new state.

サポートされている XML 属性について詳しくは、上記のクラスをご覧ください。

シェイプ ドローアブル

ShapeDrawable オブジェクトは、2 次元のグラフィックを動的に描画する場合に適しています。プログラムで ShapeDrawable オブジェクト上にプリミティブ図形を描画し、アプリに必要なスタイルを適用できます。

ShapeDrawableDrawable のサブクラスです。このため、Drawable が想定される場所であればどこでも ShapeDrawable を使用できます。たとえば、ShapeDrawable オブジェクトを使ってビューの setBackgroundDrawable() メソッドに渡すことで、ビューの背景を設定できます。また、シェイプを独自のカスタムビューとして描画し、アプリのレイアウトに追加することもできます。

ShapeDrawable には独自の draw() メソッドがあるため、次のコードサンプルに示すように、onDraw() イベント中に ShapeDrawable オブジェクトを描画する View のサブクラスを作成できます。

Kotlin

class CustomDrawableView(context: Context) : View(context) {
    private val drawable: ShapeDrawable = run {
        val x = 10
        val y = 10
        val width = 300
        val height = 50
        contentDescription = context.resources.getString(R.string.my_view_desc)

        ShapeDrawable(OvalShape()).apply {
            // If the color isn't set, the shape uses black as the default.
            paint.color = 0xff74AC23.toInt()
            // If the bounds aren't set, the shape can't be drawn.
            setBounds(x, y, x + width, y + height)
        }
    }

    override fun onDraw(canvas: Canvas) {
        drawable.draw(canvas)
    }
}

Java

public class CustomDrawableView extends View {
  private ShapeDrawable drawable;

  public CustomDrawableView(Context context) {
    super(context);

    int x = 10;
    int y = 10;
    int width = 300;
    int height = 50;
    setContentDescription(context.getResources().getString(
            R.string.my_view_desc));

    drawable = new ShapeDrawable(new OvalShape());
    // If the color isn't set, the shape uses black as the default.
    drawable.getPaint().setColor(0xff74AC23);
    // If the bounds aren't set, the shape can't be drawn.
    drawable.setBounds(x, y, x + width, y + height);
  }

  protected void onDraw(Canvas canvas) {
    drawable.draw(canvas);
  }
}

上記のコードサンプルの CustomDrawableView クラスは、他のカスタムビューと同じように使用できます。たとえば、次の例に示すように、アプリのアクティビティにプログラムで追加できます。

Kotlin

private lateinit var customDrawableView: CustomDrawableView

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    customDrawableView = CustomDrawableView(this)

    setContentView(customDrawableView)
}

Java

CustomDrawableView customDrawableView;

protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  customDrawableView = new CustomDrawableView(this);

  setContentView(customDrawableView);
}

代わりに XML レイアウトでカスタムビューを使用する場合は、CustomDrawableView クラスで View(Context, AttributeSet) コンストラクタをオーバーライドする必要があります。このコンストラクタは、クラスが XML からインフレートされるときに呼び出されます。次の例は、XML レイアウトで CustomDrawableView を宣言する方法を示しています。

<com.example.shapedrawable.CustomDrawableView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        />

ShapeDrawable クラスでは、android.graphics.drawable パッケージの他の多くのドローアブル タイプと同様に、パブリック メソッドを使用してオブジェクトのさまざまなプロパティを定義できます。調整できるプロパティの例としては、アルファ透明度、カラーフィルタ、ディザ、不透明度、色などがあります。

XML リソースを使用して基本的なドローアブル図形を定義することもできます。詳しくは、 ドローアブル リソースタイプ シェイプ ドローアブルをご覧ください。

NinePatch ドローアブル

NinePatchDrawable グラフィックは、ビューの背景として使用できる伸縮可能なビットマップ画像です。Android は、ビューのコンテンツに合わせてグラフィックのサイズを自動的に変更します。NinePatch 画像の使用例には、標準の Android ボタンで使用される背景があります。ボタンは、さまざまな長さの文字列に合わせて拡大する必要があります。NinePatch グラフィックは、1 ピクセルの枠線を含む標準の PNG 画像です。9.png 拡張子を使用して、プロジェクトの res/drawable/ ディレクトリに保存する必要があります。

枠線を使用して、画像の伸縮可能領域と静止領域を定義します。伸縮可能なセクションを指定するには、枠線の左側と上部に幅 1 ピクセルの黒い線を 1 本以上描画します(他の枠線ピクセルは完全に透明または白にします)。伸縮可能なセクションは必要な数だけ作成できます。伸縮可能なセクションの相対的なサイズは同じであるため、最大セクションは常に最大のままです。

右側に線を、下部に線を描画することで、画像のオプションのドローアブル セクション(実際にはパディング ライン)を定義することもできます。View オブジェクトが NinePatch グラフィックを背景として設定し、ビューのテキストを指定すると、すべてのテキストが右と下の線(含まれている場合)で指定された領域のみを占有するように伸縮します。パディング ラインが含まれていない場合、Android は左と上の線を使用してこのドローアブル領域を定義します。

線の違いを明確にするため、左と上の線は、画像を拡大するために複製できる画像のピクセルを定義しています。下と右の線は、ビューのコンテンツが占有できる画像内の相対領域を表します。

図 1 は、ボタンの定義に使用する NinePatch グラフィックの例を示しています。

伸縮可能領域とパディング ボックスの画像

図 1: ボタンを定義する NinePatch グラフィックの例

この NinePatch グラフィックは、左と上の線で伸縮可能領域を 1 つ定義し、下線と右線でドローアブル領域を定義しています。上の画像では、グレーの点線は、画像を拡大するために複製される画像領域を示しています。下の画像にあるピンク色の長方形は、ビューのコンテンツが許可されているリージョンを示しています。コンテンツがこの領域に収まらない場合は、画像が収まるように引き伸ばされます。

Draw 9-patch ツールを使用すると、WYSIWYG グラフィック エディタを使用して NinePatch 画像を簡単に作成できます。さらに、伸縮可能な領域に定義した領域で、ピクセル レプリケーションの結果として描画アーティファクトが発生するリスクがある場合は、警告が表示されます。

次のサンプル レイアウト XML は、NinePatch グラフィックをいくつかのボタンに追加する方法を示しています。NinePatch 画像は res/drawable/my_button_background.9.png に保存されます。

<Button android:id="@+id/tiny"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerInParent="true"
        android:text="Tiny"
        android:textSize="8sp"
        android:background="@drawable/my_button_background"/>

<Button android:id="@+id/big"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerInParent="true"
        android:text="Biiiiiiig text!"
        android:textSize="30sp"
        android:background="@drawable/my_button_background"/>

layout_width 属性と layout_height 属性を wrap_content に設定して、ボタンがテキストの周りにぴったりと収まるようにします。

図 2 は、上の XML と NinePatch 画像からレンダリングされた 2 つのボタンを示しています。ボタンの幅と高さはテキストによって異なり、背景画像はそれに合わせて拡大されます。

小さいサイズのボタンと
標準サイズのボタンの画像

図 2: XML リソースと NinePatch グラフィックを使用してレンダリングされたボタン

カスタム ドローアブル

カスタムの図形描画を作成する場合は、Drawable クラス(またはそのサブクラス)を拡張します。

実装すべき最も重要なメソッドは draw(Canvas) です。これは、描画手順を提供するために使用する Canvas オブジェクトが提供されるためです。

次のコードは、円を描画する Drawable の単純なサブクラスを示しています。

Kotlin

class MyDrawable : Drawable() {
    private val redPaint: Paint = Paint().apply { setARGB(255, 255, 0, 0) }

    override fun draw(canvas: Canvas) {
        // Get the drawable's bounds
        val width: Int = bounds.width()
        val height: Int = bounds.height()
        val radius: Float = Math.min(width, height).toFloat() / 2f

        // Draw a red circle in the center
        canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, redPaint)
    }

    override fun setAlpha(alpha: Int) {
        // This method is required
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
        // This method is required
    }

    override fun getOpacity(): Int =
        // Must be PixelFormat.UNKNOWN, TRANSLUCENT, TRANSPARENT, or OPAQUE
        PixelFormat.OPAQUE
}

Java

public class MyDrawable extends Drawable {
    private final Paint redPaint;

    public MyDrawable() {
        // Set up color and text size
        redPaint = new Paint();
        redPaint.setARGB(255, 255, 0, 0);
    }

    @Override
    public void draw(Canvas canvas) {
        // Get the drawable's bounds
        int width = getBounds().width();
        int height = getBounds().height();
        float radius = Math.min(width, height) / 2;

        // Draw a red circle in the center
        canvas.drawCircle(width/2, height/2, radius, redPaint);
    }

    @Override
    public void setAlpha(int alpha) {
        // This method is required
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        // This method is required
    }

    @Override
    public int getOpacity() {
        // Must be PixelFormat.UNKNOWN, TRANSLUCENT, TRANSPARENT, or OPAQUE
        return PixelFormat.OPAQUE;
    }
}

これで、以下のように、ImageView など、任意の場所にドローアブルを追加できます。

Kotlin

val myDrawing = MyDrawable()
val image: ImageView = findViewById(R.id.imageView)
image.setImageDrawable(myDrawing)
image.contentDescription = resources.getString(R.string.my_image_desc)

Java

MyDrawable mydrawing = new MyDrawable();
ImageView image = findViewById(R.id.imageView);
image.setImageDrawable(mydrawing);
image.setContentDescription(getResources().getString(R.string.my_image_desc));

Android 7.0(API レベル 24)以降では、次の方法で XML でカスタム ドローアブルのインスタンスを定義することもできます。

  • XML 要素名として完全修飾クラス名を使用する。このアプローチでは、カスタム ドローアブル クラスはパブリック トップレベル クラスである必要があります。
    <com.myapp.MyDrawable xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="#ffff0000" />
    
  • XML タグ名として drawable を使用し、クラス属性の完全修飾クラス名を指定する。この方法は、パブリック トップレベル クラスとパブリック静的内部クラスの両方に使用できます。
    <drawable xmlns:android="http://schemas.android.com/apk/res/android"
        class="com.myapp.MyTopLevelClass$MyDrawable"
        android:color="#ffff0000" />
    

ドローアブルに色合いを追加する

Android 5.0(API レベル 21)以降では、アルファマスクとして定義されたビットマップと 9-patch の色合いを調整できます。カラーリソースまたはカラーリソースに変換されるテーマ属性(?android:attr/colorPrimary など)を使用して着色できます。通常は、これらのアセットを一度だけ作成し、テーマに合わせて自動的に色付けします。

setTint() メソッドを使用して、BitmapDrawableNinePatchDrawable、または VectorDrawable オブジェクトに色合いを適用できます。android:tint 属性と android:tintMode 属性を使用して、レイアウトの色合いとモードを設定することもできます。

画像から代表色を抽出する

Android サポート ライブラリには Palette クラスが含まれています。このクラスを使用すると、画像から目立つ色を抽出できます。ドローアブルを Bitmap として読み込み、Palette に渡して色にアクセスできます。詳しくは、Palette API で色を選択するをご覧ください。