ドローアブルの概要

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 でオブジェクトを定義することを検討してください。

XML で Drawable を定義したら、ファイルをプロジェクトの 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"
        />

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

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 など)を使用して着色できます。通常は、これらのアセットを 1 回だけ作成し、テーマに合わせて自動的に色付けします。

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

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

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