Android アプリ開発:DrawableフォルダにおけるカスタムDrawableの実装

Drawable とは

Drawable は、Canvas 上で描画可能な抽象概念です。色や画像など、視覚的な要素はすべて Drawable の一種とみなせます。

  • XML による定義と、プログラムコードによる動的生成が可能です。
  • Android の Drawable は抽象クラスであり、具体的な描画処理はそのサブクラスが担います。

Drawable の利点

  • 簡易的に視覚要素を定義でき、自定义 View を作るより開発コストが低い。
  • 非画像系の Drawable(ShapeDrawable など)はファイルサイズが小さく、APK の肥大を抑えられます。

内部サイズ(Intrinsic Size)について

  • 대부분の Drawable は getIntrinsicWidth()getIntrinsicHeight() で内部サイズを取得できます。
  • BitmapDrawable では画像そのもののサイズが内部サイズになります。
  • ColorDrawable には内部サイズの概念がありません(-1 を返します)。
  • 内部サイズと表示サイズは異なります。たとえば View の背景に設定された場合は、View の寸法に合わせて引き伸ばされます。

主要な Drawable クラスとその利用法

BitmapDrawable

画像リソースを表現する Drawable です。

<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@drawable/your_image"
    android:antialias="true"
    android:dither="true"
    android:filter="true"
    android:gravity="center"
    android:tileMode="disabled" />

主な属性:

属性設定内容備考
android:src画像リソース ID必須
android:antialiasアンチエイリアスの有無推奨
android:ditherディザリングの有無推奨
android:filterスケーリング時の補間処理推奨
android:gravity表示位置の指定複数指定可
android:tileMode平铺モード(repeat/mirror/clamp)デフォルトは disable

NinePatchDrawable(.9 画像)

特定の領域だけを伸縮させる画像で、UI 要素のサイズ変更時に画質の劣化を避けられます。

ShapeDrawable

幾何学的な図形を XML で記述する手段です。実体は GradientDrawable です。

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    
    <corners android:radius="8dp" />
    
    <gradient
        android:type="linear"
        android:startColor="@color/red"
        android:endColor="@color/blue"
        android:angle="45"/>
    
    <stroke
        android:width="2dp"
        android:color="@color/stroke_color"
        android:dashWidth="4dp"
        android:dashGap="2dp"/>
    
    <size
        android:width="100dp"
        android:height="100dp"/>

</shape>

主な要素:

  • <corners>:角の丸み設定
  • <gradient>:グラデーション(線形、径向、スキャン)
  • <solid>:単色描画(gradient と併用不可)
  • <stroke>:線(実線/虚線)
  • <size>:内部サイズの明示

LayerDrawable

複数の Drawable を重ねて表示するためのクラスで、XML では <layer-list> を利用します。

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle">
            <solid android:color="#0ac39e"/>
        </shape>
    </item>
    
    <item android:bottom="6dp">
        <shape android:shape="rectangle">
            <solid android:color="#ffffff"/>
        </shape>
    </item>
</layer-list>

StateListDrawable

View の状態(pressed/focused/enabled など)に応じて Drawable を切り替える機能です。XML では <selector> を使います。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true"
        android:drawable="@color/pressed_bg"/>
    <item android:state_focused="true"
        android:drawable="@color/focused_bg"/>
    <item android:drawable="@color/default_bg"/>
</selector>

LevelListDrawable

level(0~10000)に応じて表示する Drawable を切り替えるものです。

<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:minLevel="0" android:maxLevel="100"
        android:drawable="@drawable/low_battery"/>
    <item android:minLevel="101" android:maxLevel="500"
        android:drawable="@drawable/mid_battery"/>
    <item android:minLevel="501" android:maxLevel="10000"
        android:drawable="@drawable/high_battery"/>
</level-list>

TransitionDrawable

2 つの Drawable 間でフェードアニメーションを実行します。

<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/image1"/>
    <item android:drawable="@drawable/image2"/>
</transition>

実行例:

val transition = findViewById(R.id.imageView).drawable as TransitionDrawable
transition.startTransition(300)

ClipDrawable

指定された方向から徐々にオブジェクトを表示/削除できるcovering animation 用の Drawable です。

主なパラメータ:

  • android:clipOrientation:clip 方向(horizontal/vertical)
  • android:gravity:clip 基準位置

AnimationDrawable

逐次アニメーション(フレーム単位)を実現します。XML は <animation-list> で記述します。

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item android:drawable="@drawable/frame1" android:duration="100"/>
    <item android:drawable="@drawable/frame2" android:duration="100"/>
    ...
</animation-list>

実行:

val animatable = imageView.drawable as AnimationDrawable
animatable.start()

その他のDrawable

クラスXMLタグ用途
ColorDrawable<color>単色描画
RotateDrawable<rotate>回転表示
RippleDrawable<ripple>Touch feedback(API 21+)
VectorDrawable<vector>SVG形式の静的ベクター画像
AnimatedVectorDrawable<animated-vector>ベクター画像のアニメーション
AnimatedStateListDrawable<animated-selector>状態変化時のアニメーション

SVGベースのベクター描画(VectorDrawable)

Android 5.0(API 21)で導入されたベクター画像実装で、解像度に依存せず拡大しても劣化しません。

基本構成

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24">

    <group>
        <path
            android:name="arrow"
            android:pathData="M12,2 L2,22 L22,22 Z"
            android:fillColor="#FF000000"/>
    </group>
</vector>

主要な SVG 命令一覧

コマンド説明
M(moveto)描画開始位置へ移動
L(lineto)直線描画
H/V(horizontal/vertical lineto)軸方向への直線
C(curveto)3次ベジェ曲線
A(elliptical arc)楕円弧描画
Z(closepath)パスを閉じる

AnimatedVectorDrawable によるアニメーション

ベクター画像の path データや描画プロパティをアニメーションさせる仕組みです。

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/vector_drawable+xml">

    <target
        android:name="path1"
        android:animation="@animator/trim_path"/>
</animated-vector>

アニメーション定義例(trimPath):

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:propertyName="trimPathEnd"
    android:valueFrom="0"
    android:valueTo="1"
    android:duration="500"
    android:interpolator="@android:interpolator/accelerate_decelerate"/>

カスタム Drawable の実装

独自の視覚要素を実現するには、Drawable クラスを継承し、draw() メソッドをオーバーライドします。

class CustomCircleDrawable(private val color: Int) : Drawable() {
    private val paint = Paint().apply {
        isAntiAlias = true
        this.color = color
    }

    override fun draw(canvas: Canvas) {
        val b = bounds
        canvas.drawCircle(
            b.exactCenterX(),
            b.exactCenterY(),
            minOf(b.width(), b.height()) / 2f,
            paint
        )
    }

    override fun setAlpha(alpha: Int) {
        paint.alpha = alpha
        invalidateSelf()
    }

    override fun setColorFilter(filter: ColorFilter?) {
        paint.colorFilter = filter
        invalidateSelf()
    }

    override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
}

注意点:

  • getIntrinsicWidth()getIntrinsicHeight() を実装する必要がある場合(wrap_content 対応時)
  • 描画内容の変更タイミングを invalidateSelf() で通知すること

ShapeDrawable の拡張サブクラス

ShapeDrawable を直接利用した描画の他、以下のような具象サブクラスも用意されています:

val rectShape = RectShape()
val rectDrawable = ShapeDrawable(rectShape).apply {
    paint.color = Color.RED
    paint.style = Paint.Style.FILL
}

val ovalShape = OvalShape()
val ovalDrawable = ShapeDrawable(ovalShape).apply {
    paint.color = Color.BLUE
}

val arcShape = ArcShape(startAngle = 45f, sweepAngle = 180f)
val arcDrawable = ShapeDrawable(arcShape).apply {
    paint.color = Color.YELLOW
}

また、RoundRectShape を継承した PaintDrawable を使えば、角丸矩形の描画をプログラムからも簡潔に実現できます。

タグ: Android Drawable VectorDrawable SVG ShapeDrawable

5月17日 10:03 投稿