カスタム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 = 75fCompound 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フィードバックを取得できます。ローカル設定は不要です。