AndroidのCustom View — 本質、描画、イベント処理

著者: IT Sectr 公開日: 2026-07-20 読了時間: 9 分

Custom Viewは、Viewクラスまたはそのサブクラス(Button、TextView、ImageView)を継承し、主要なメソッド(描画用のonDraw、サイズ測定用のonMeasure、タッチ処理用のonTouchEvent)をオーバーライドして作成される、Androidのカスタムインターフェースコンポーネントです。Google Android Developer Guide(2024)によると、標準のAndroid SDKコンポーネントが必要な動作や外観を提供しない場合(カスタムアニメーション、非標準形状、特殊なグラフ、ゲーム要素など)にCustom Viewが使用されます。

重要なポイント

  • Custom View — Viewまたはそのサブクラスを継承し、onDraw、onMeasure、onTouchEventをオーバーライドして作成します。
  • 描画はCanvas APIを介して実行されます — 幾何学図形、テキスト、パス、Bitmap、アニメーション。
  • onMeasureはレイアウトパラメータとパディングを考慮してViewの正しいサイズを処理します。
  • カスタムXML属性はres/values/attrs.xmlで宣言され、Viewコンストラクタで読み取られます。
  • onTouchEventによるタッチ処理により、ジェスチャサポート、ドラッグ&ドロップ、マルチタッチを追加できます。

AndroidのCustom Viewとは?

Custom Viewは、android.view.View(またはそのサブクラス)を継承し、システムメソッドをオーバーライドしてカスタムの描画、測定、イベント処理ロジックを実装するクラスです。Custom Viewは、独自のUIコンポーネントを作成するためのAndroidの基本的なメカニズムです。

AndroidはCustom Viewを作成する2つのアプローチを提供します:Viewを継承する方法(onDrawによる完全カスタム描画)と、既存のViewサブクラスを継承する方法(例えば、基本動作を維持しながら機能を追加するためにButtonやTextViewをオーバーライド)。

基本コンストラクタはContextとAttributeSetを受け入れる必要があります — これによりシステムがXMLマークアップからViewを作成できます。Viewがコードからのみ使用される場合は、Contextのみのコンストラクタで十分です。スタイル付きの第3コンストラクタはAndroidテーマのサポートに必要です。

kotlin
class CustomView
    @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.BLUE
        strokeWidth = 4f
        style = Paint.Style.FILL
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawCircle(width / 2f,
            height / 2f,
            50f, paint)
    }
}

最小限のCustom ViewはコンストラクタとonDrawで構成されます。ただし、AndroidのLayoutシステムで正しく動作するには、onMeasureもオーバーライドする必要があります — そうしないと、Viewがゼロサイズで表示されたり、wrap_contentレイアウトパラメータに誤って反応したりする可能性があります。

Google I/O 2023によると、Google Playのトップ100アプリの65%でカスタムViewが使用されています。Custom Viewを作成する最も一般的な理由:カスタムアニメーション(35%)、カスタムチャートとダイアグラム(25%)、特殊な制御要素(20%)、ブランドコンポーネント(20%)。

onMeasureとonLayout:サイズ管理

onMeasureは、受け取ったMeasureSpec(親の制約)に基づいてViewが希望する寸法を報告するメソッドです。適切なonMeasureの実装がないと、wrap_contentでViewの高さがゼロになったり、match_parentで画面全体を占めたりする可能性があります。

MeasureSpecはモード(EXACTLY、AT_MOST、UNSPECIFIED)と値で構成されます。EXACTLY — 親が正確なサイズを設定(match_parentまたは固定値)。AT_MOST — 親が最大値を設定(wrap_content)。UNSPECIFIED — 制限なし(ScrollView、ListView)。

kotlin
override fun onMeasure(
    widthMeasureSpec: Int,
    heightMeasureSpec: Int
) {
    val desiredWidth = paddingLeft + paddingRight
        + DEFAULT_WIDTH
    val desiredHeight = paddingTop + paddingBottom
        + DEFAULT_HEIGHT

    val measuredWidth = MeasureSpec.getSize(widthMeasureSpec)
    val modeWidth = MeasureSpec.getMode(widthMeasureSpec)

    val resultWidth = when (modeWidth) {
        MeasureSpec.EXACTLY -> measuredWidth
        MeasureSpec.AT_MOST ->
            desiredWidth.coerceAtMost(measuredWidth)
        else -> desiredWidth
    }

    val resultHeight = resolveSize(desiredHeight,
        heightMeasureSpec)

    setMeasuredDimension(resultWidth, resultHeight)
}

resolveSizeは、onMeasureの実装を簡素化するAndroidのユーティリティメソッドです。希望サイズとMeasureSpecを受け取り、正しい値を返します:EXACTLYの場合は正確なサイズ、AT_MOSTの場合は希望と最大の最小値、UNSPECIFIEDの場合は希望サイズ。

onLayoutはView(ViewGroupではない)の場合、通常はオーバーライドされません — 子要素の配置のために親から呼び出されます。ViewGroupの場合、onLayoutは必須です — 各子Viewに対してlayout()を呼び出す必要があります。

onDrawとCanvas API:描画

onDrawはCustom Viewの心臓部です。このメソッドはViewの再描画のたびにAndroidから呼び出されます。Canvasオブジェクトが渡され、それを通じてすべての描画(幾何学図形、テキスト、パス、Bitmap、アニメーション)が実行されます。

Canvas APIはすべての基本操作のメソッドを提供します:drawCircle、drawRect、drawLine、drawPath、drawText、drawBitmap、drawArc、drawOval。スタイルを設定するには、色、太さ、塗りつぶしスタイル、フォント、影、効果を定義するオブジェクトであるPaintが使用されます。

kotlin
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)

    // 背景
    canvas.drawRect(0f, 0f,
        width.toFloat(), height.toFloat(), bgPaint)

    // テキスト
    canvas.drawText("Custom View",
        paddingLeft.toFloat(),
        paddingTop.toFloat() + textPaint.textSize,
        textPaint)

    // 円
    canvas.drawCircle(
        width / 2f,
        height / 2f,
        radius, circlePaint)

    // パス(ベジェ曲線)
    val path = Path().apply {
        moveTo(0f, height.toFloat())
        quadTo(width / 2f, 0f,
            width.toFloat(), height.toFloat())
    }
    canvas.drawPath(path, pathPaint)
}

Paintは描画スタイル設定器です。アンチエイリアシング(Paint.ANTI_ALIAS_FLAG)は滑らかなエッジに必須です。サブピクセルテキスト(SUBPIXEL_TEXT_FLAG)はテキスト品質を向上させます。Style.FILL、STROKE、FILL_AND_STROKEは、図形を塗りつぶすか、輪郭を描くか、その両方を決定します。

重要:onDraw内でPaint、Path、Rectオブジェクトを作成しないでください — 描画ループでの割り当てを引き起こし、ガベージコレクションを誘発してFPSを低下させます。すべての描画オブジェクトはコンストラクタまたはinitメソッドで作成してください。アニメーションには、再描画をトリガーするためにValueAnimatorまたはObjectAnimatorをinvalidate()とともに使用します。

attrs.xmlのカスタムXML属性

カスタム属性を使用すると、標準のandroid:layout_widthやandroid:backgroundと同様に、XMLマークアップからCustom Viewを設定できます。属性はres/values/attrs.xmlファイルで名前、タイプ、オプションのデフォルト値とともに宣言されます。

属性のタイプ:string、integer、float、boolean、color、dimension、enum、flag、fraction、reference(リソース参照)。各タイプに対して、Androidは自動的にXMLから値を解析してTypedArrayに渡します。

xml
<!-- res/values/attrs.xml -->
<resources>
    <declare-styleable name="CustomView">
        <attr name="circleColor"
            format="color" />
        <attr name="circleRadius"
            format="dimension" />
        <attr name="labelText"
            format="string" />
        <attr name="showAnimation"
            format="ブール値" />
    </declare-styleable>
</resources>

Custom Viewのコンストラクタでは、属性はcontext.obtainStyledAttributesを介して読み取られ、TypedArrayが返されます。TypedArrayは型付きアクセスメソッドを提供します:getColor、getDimension、getString、getBoolean、getInt。読み取り後、リソースを解放するためにTypedArrayでrecycle()を呼び出す必要があります。

kotlin
init {
    val typedArray = context.obtainStyledAttributes(
        attrs,
        R.styleable.CustomView
    )

    circleColor = typedArray.getColor(
        R.styleable.CustomView_circleColor,
        Color.BLUE
    )
    circleRadius = typedArray.getDimension(
        R.styleable.CustomView_circleRadius,
        50f
    )
    labelText = typedArray.getString(
        R.styleable.CustomView_labelText
    ) ?: "Default"

    typedArray.recycle()
}

XMLでの使用:app名前空間を追加し(xmlns:app="http://schemas.android.com/apk/res-auto")、app:circleColor="@color/red"のようにカスタム属性を使用します。attrs.xmlの宣言が正しければ、Android Studioが自動補完し、属性タイプを検証します。

onTouchEventによるタッチ処理

onTouchEventは、Viewがタッチされるたびに呼び出されるメソッドです。イベントタイプ(ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCEL)、座標、圧力、指の数(マルチタッチ)に関する情報を含むMotionEventオブジェクトを受け取ります。

複雑なジェスチャ(スワイプ、ピンチ、長押し)を処理するには、onTouchEventと組み合わせてGestureDetectorまたはScaleGestureDetectorを使用します。GestureDetectorはonSingleTapUp、onFling、onLongPress、onDoubleTapなどの標準ジェスチャの認識を簡素化します。

kotlin
private val gestureDetector = GestureDetector(
    context, object : GestureDetector.SimpleOnGestureListener() {
    override fun onSingleTapUp(e: MotionEvent): Boolean {
        handleTap(e.x, e.y)
        return true
    }
})

override fun onTouchEvent(event: MotionEvent): Boolean {
    val handled = gestureDetector.onTouchEvent(event)

    when (event.action) {
        MotionEvent.ACTION_MOVE -> {
            currentX = event.x
            currentY = event.y
            invalidate()
            return true
        }
    }

    return handled || super.onTouchEvent(event)
}

ヒットテスト — タッチがViewの特定の領域に当たったかどうかを判断します。長方形領域の場合はRect.contains(x, y)を使用します。円の場合は中心からの距離を確認:sqrt(dx^2 + dy^2) < radius。任意の形状の場合はPath.op()またはRegion.contains()。

マルチタッチはMotionEvent.getPointerCount()とgetPointerId(index)を介して処理されます。各指はACTION_DOWNからACTION_POINTER_UPまで保持される一意のIDを取得します。ACTION_MOVEはすべてのアクティブな指のデータを含む場合があります — 移動の補間にはgetHistoricalX/Yを使用します。

Custom Viewのパフォーマンス最適化

Custom Viewは、onDrawが頻繁に呼び出される場合(アニメーション、スクロール)や重い操作を含む場合に、パフォーマンスのボトルネックになる可能性があります。Androidはカスタムコンポーネントのレンダリングを最適化するためのいくつかのメカニズムを提供しています。

複雑な描画のためのレイヤータイプ

View.setLayerTypeを使用すると、Viewのレンダリングをソフトウェアレイヤー(LAYER_TYPE_SOFTWARE)またはハードウェアレイヤー(LAYER_TYPE_HARDWARE)に切り替えられます。ソフトウェアレイヤーは、ハードウェアアクセラレーションでサポートされていない複雑なCanvasグラフィック(drawTextOnPathや複雑なPath効果など)に役立ちます。

必要な領域のみを無効化

invalidate(Rect)は、コンポーネント全体ではなく、Viewの指定された領域のみを再描画します。これは、大きなCustom View(グラフィック、マップ、描画キャンバス)にとって非常に重要であり、毎フレームの完全な再描画がFPSの低下を引き起こします。vsync同期にはpostInvalidateOnAnimation()を使用します。

kotlin
class EfficientCustomView
    @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : View(context, attrs) {

    // コンストラクタで一度作成されるオブジェクト
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val rect = Rect()
    private val position = PointF()

    fun updatePosition(x: Float, y: Float) {
        position.set(x, y)
        // ポイント周辺の領域のみ再描画
        rect.set(
            (x - 10).toInt(),
            (y - 10).toInt(),
            (x + 10).toInt(),
            (y + 10).toInt()
        )
        invalidate(rect)
    }
}

ハードウェアアクセラレーションはAndroid 3.0+(API 11+)でデフォルトで有効です。Canvas操作のdrawCircle、drawRect、drawBitmapはハードウェアアクセラレーションされ、GPUで実行されます。ただし、drawTextOnPath、drawVertices、複雑なクリッピング操作はアクセラレーションされません — これらにはLAYER_TYPE_SOFTWAREを使用します。

Android Performance Patterns(Google、2023)によると、Custom Viewの遅延の主な原因 — onDrawでの割り当て(毎フレームのオブジェクト作成)、1つの要素変更時のView全体の再描画、静的コンテンツに対するLayerType.HARDWAREの欠如。これら3つの問題を修正すると、ほとんどのシナリオでFPSが30から60に向上します。

メソッド目的呼び出し頻度
onDrawViewの内容を描画invalidate()ごと
onMeasureViewのサイズを決定レイアウト変更時
onLayout子Viewの配置onMeasure後
onTouchEventタッチ処理タッチごと
onSizeChangedサイズ変更への反応最初のレイアウトと変更時

よくある質問

Composeの代わりにCustom Viewを使用するのはいつ?

Custom View(Canvas API)は、最大のカスタム描画パフォーマンス(グラフィック、ビデオエディタ、マップ)が必要な場合、既存のViewベースのコードとの統合、またはAPI 21未満のAndroidバージョンのサポートが必要な場合に適しています。Jetpack Composeはほとんどの新しいプロジェクトのための最新のアプローチであり、宣言的UIを使用し、動的インターフェースにより適しています。

onMeasureをオーバーライドする必要はありますか?

はい、ViewがXMLレイアウトでwrap_contentを使用する場合。onMeasureがないと、デフォルトのView.onMeasure実装がデフォルトサイズを設定しないため、wrap_contentはmatch_parentのように動作します。Viewが常に固定サイズまたはmatch_parentを持つ場合は、onMeasureをオーバーライドしなくても問題ありません。

Custom Viewをアニメーション化するには?

ValueAnimatorまたはObjectAnimatorを使用してViewのプロパティ(色、半径、位置)を変更し、アニメーションコールバックでinvalidate()を呼び出して再描画します。ValueAnimatorはメインスレッドで実行され、vsyncと同期しています。複雑なアニメーション(物理、パーティクル)の場合は、Choreographer.FrameCallbackまたはAndroid Animation Frameworkを使用します。

Custom Viewでマルチタッチを処理するには?

onTouchEventで、MotionEvent.getPointerCount()を使用して指の数を判断し、getPointerId(i)を使用して各指を一意のIDで追跡します。ACTION_POINTER_DOWN / ACTION_POINTER_UP — 指の追加/削除イベント。ピンチズームには、onTouchEventと組み合わせてScaleGestureDetectorを使用します — ピンチ検出を簡素化します。

Jetpack ComposeでCustom Viewを使用できますか?

はい、AndroidViewを介して使用できます — Viewベースの要素をCompose階層に埋め込むコンポーザブル関数です。AndroidViewはViewを作成するためのファクトリと状態同期のためのupdateコールバックを受け入れます。これにより、完全な書き換えなしで既存のCustom Viewを新しいComposeプロジェクトで再利用できます。

まとめ

  • Custom View — カスタム描画と処理のためにonDraw、onMeasure、onTouchEventをオーバーライドしてViewを継承して作成されるカスタムAndroidコンポーネント。
  • onDrawとCanvas API — 描画の基礎:幾何学図形、テキスト、Bitmap、パス — 色、スタイル、フォントのためにPaintを介して設定。
  • onMeasureは正しいwrap_content動作に重要。MeasureSpec(EXACTLY、AT_MOST、UNSPECIFIED)とresolveSizeを使用して寸法を計算。
  • カスタム属性はattrs.xmlとTypedArrayを介して、Android Studioの自動補完サポート付きでXMLマークアップからCustom Viewを設定可能。
  • onTouchEventとGestureDetectorによりタッチ、ジェスチャ、マルチタッチ処理を提供し、ヒットテストでインタラクション領域を決定。
  • パフォーマンス:コンストラクタで一度オブジェクトを作成し、部分的な再描画にinvalidate(Rect)を、複雑な効果にLayerTypeを使用。
  • Custom ViewはAndroidViewを介してJetpack Composeと統合され、既製のコンポーネントを新しいComposeプロジェクトで再利用可能。

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください