0Pricing
Android Academy · レッスン

カスタムView

UIコンポーネントをゼロから描画します。Viewを継承し、CanvasとPaintを使ってonMeasureとonDrawを実装し、カスタムXML属性を定義して、複合Viewを構築します。

「カスタムView」はCoddyKit上の無料Android Academyレッスンです。 これはレッスン3/5です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAndroid Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Android Academyコースには全5レッスンが含まれています。

カスタムViewを使う理由

組み込みのAndroid Viewでは、必要なことを正確に実現できない場合があります。カスタムViewを使うと、次のことができます:

  • SDKに存在しない図形、グラフ、プログレスリング、チャートを描画する
  • アプリ全体で一貫して使用できるブランドUIコンポーネントを作成する
  • 複数のViewを1つの再利用可能なコンポーネント(Compound View)にまとめる
  • ネストされたレイアウト階層をなくしてパフォーマンスを向上させる

Viewの拡張

View(またはImageViewなどのサブクラス)を拡張してカスタムViewを作成します。必要なコンストラクターをオーバーライドします:

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

    var progress: Float = 0f
        set(value) {
            field = value.coerceIn(0f, 100f)
            invalidate()   // trigger redraw
        }

    // onMeasure and onDraw go here
}

onMeasure()

onMeasure()は、このViewが必要とするサイズを親に伝えます。最後に必ずsetMeasuredDimension()を呼び出します:

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val desiredSize = 200  // dp → pixels
    val px = (desiredSize * resources.displayMetrics.density).toInt()

    val width  = resolveSize(px, widthMeasureSpec)
    val height = resolveSize(px, heightMeasureSpec)

    // Square: same width and height
    val size = minOf(width, height)
    setMeasuredDimension(size, size)
}

Paint — 描画ツール

Paintオブジェクトには、描画に使用するスタイル情報(色、ストローク幅、アンチエイリアスなど)が格納されます。1回だけ作成し、onDraw()内では作成しないでください:

class CircleProgressView ... : View(...) {

    // Create Paint in init, not in onDraw!
    private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.LTGRAY
        style = Paint.Style.STROKE
        strokeWidth = 20f
        strokeCap = Paint.Cap.ROUND
    }

    private val progressPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.parseColor("#6750A4")
        style = Paint.Style.STROKE
        strokeWidth = 20f
        strokeCap = Paint.Cap.ROUND
    }
}

onDraw() — Viewの描画

onDraw(canvas)は、Viewを再描画する必要があるたびに呼び出されます。Canvasを使って図形、テキスト、ビットマップを描画します:

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

    val cx = width / 2f
    val cy = height / 2f
    val radius = (minOf(width, height) / 2f) - 30f

    // Draw background circle
    canvas.drawCircle(cx, cy, radius, backgroundPaint)

    // Draw progress arc
    val oval = RectF(cx - radius, cy - radius, cx + radius, cy + radius)
    val sweepAngle = 360f * (progress / 100f)
    canvas.drawArc(oval, -90f, sweepAngle, false, progressPaint)
}

invalidate()とrequestLayout()の違い

更新を発生させる2つのメソッド:

  • invalidate() — Viewを再描画します(onDrawを呼び出します)。サイズは変わらず、内容だけが変わる場合に使用します。
  • requestLayout() — 再計測して再描画します(onMeasure、続いてonDrawを呼び出します)。Viewのサイズが変わる場合に使用します。

カスタム属性(attrs.xml)

カスタムXML属性を宣言すると、レイアウトファイルからViewを設定できるようになります:

<!-- res/values/attrs.xml -->
<resources>
    <declare-styleable name="CircleProgressView">
        <attr name="progressColor" format="color" />
        <attr name="trackColor"    format="color" />
        <attr name="strokeWidth"   format="dimension" />
        <attr name="progress"      format="float" />
    </declare-styleable>
</resources>

カスタム属性の読み取り

obtainStyledAttributesを使い、コンストラクターで属性を読み取ります:

init {
    context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView).use { ta ->
        progressPaint.color = ta.getColor(
            R.styleable.CircleProgressView_progressColor,
            Color.parseColor("#6750A4")
        )
        backgroundPaint.color = ta.getColor(
            R.styleable.CircleProgressView_trackColor,
            Color.LTGRAY
        )
        val sw = ta.getDimension(R.styleable.CircleProgressView_strokeWidth, 20f)
        progressPaint.strokeWidth = sw
        backgroundPaint.strokeWidth = sw
        progress = ta.getFloat(R.styleable.CircleProgressView_progress, 0f)
    }
}

XMLでのカスタムViewの使用

完全なクラスパスとカスタム属性を指定して、レイアウトXMLでカスタムViewを使用します:

<!-- In any layout XML -->
<com.example.myapp.CircleProgressView
    android:id="@+id/progressView"
    android:layout_width="120dp"
    android:layout_height="120dp"
    app:progress="65"
    app:progressColor="@color/primary"
    app:trackColor="@color/surface_variant"
    app:strokeWidth="12dp" />

<!-- Control in code: -->
binding.progressView.progress = 75f

Compound View

Compound Viewは、既存のViewを1つの再利用可能なコンポーネントにまとめたものです。LinearLayoutなどのレイアウトクラスを拡張します:

class SearchBarView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : LinearLayout(context, attrs) {

    private val binding = ViewSearchBarBinding.inflate(LayoutInflater.from(context), this)

    init {
        orientation = HORIZONTAL
        binding.ivSearch.setOnClickListener {
            onSearchClick?.invoke(binding.etSearch.text.toString())
        }
    }

    var onSearchClick: ((String) -> Unit)? = null

    fun setHint(hint: String) { binding.etSearch.hint = hint }
}

テキストの描画

drawText()を使ってCanvasにテキストを描画します。Paint.getFontMetrics()を使うと、正確に中央揃えできます:

private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    color = Color.BLACK
    textSize = 48f
    textAlign = Paint.Align.CENTER
}

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    val cx = width / 2f
    val cy = height / 2f

    // Vertically center the text:
    val fm = textPaint.fontMetrics
    val textY = cy - (fm.ascent + fm.descent) / 2f
    canvas.drawText("${progress.toInt()}%", cx, textY, textPaint)
}

クイックチェック

データが変わってもサイズが変わらない場合、カスタムViewを再描画するにはどのメソッドを呼び出しますか?

まとめ:カスタムView

これで画面上に自由に描画できるようになりました:

  • @JvmOverloads constructorを使ってViewを拡張する
  • onMeasure() — setMeasuredDimension()で必要なサイズを通知する
  • Paint — スタイルを定義する。1回だけ作成し、onDraw()内では作成しない
  • onDraw(canvas) — 円、弧、テキスト、ビットマップを描画する
  • 再描画にはinvalidate()、再計測にはrequestLayout()を使用する
  • カスタム属性 — XML設定用にattrs.xmlで定義する
  • Compound View — レイアウトを拡張して既存のViewをまとめる

次は、アニメーションとトランジションでUIを生き生きと動かします。

よくある質問

「カスタムView」レッスンは無料ですか?

はい。「カスタムView」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Android Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Android Academyコースには全5レッスンが含まれています。

「カスタムView」で何を学びますか?

UIコンポーネントをゼロから描画します。Viewを継承し、CanvasとPaintを使ってonMeasureとonDrawを実装し、カスタムXML属性を定義して、複合Viewを構築します。 ブラウザで直接実行するハンズオンコードでAndroid Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Android Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのAndroid Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/5です。

「カスタムView」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このAndroid Academyレッスンでコードを書いて実行できますか?

はい。すべてのAndroid Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Material Designの基礎
  2. テーマとスタイル
  3. カスタムView
  4. アニメーションとトランジション
  5. ボトムナビゲーションとタブ
← Android Academyに戻る