【Kotlin/Android】カスタムViewの実装方法!onDrawや属性の使い方

【Kotlin/Android】カスタムViewの実装方法!onDrawや属性の使い方

この記事からわかること

  • Android Studio/KotlinカスタムView実装する方法
  • onDrawメソッドとは?
  • カスタム属性定義

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

公式リファレンス:Create custom view components

カスタムビューを実装する

Androidアプリでは標準のビューでは対応できない特別なユーザーインターフェース要素や動作を実現するためにカスタムビューを実装することができるようになっています。

カスタムビューで定義することで独自のデザインやユーザーインタラクション(タッチやドラッグなど)の処理を細かくコントロールすることができるようになるのが大きなメリットになります。

カスタムビュー管理クラスを定義する

プロジェクト内でカスタムビューを活用するためには「New」>「 UiComponent」>「Custom View」からカスタムビュークラス名を自動生成することが可能です。

【Kotlin/Android】カスタムViewの実装方法!onDrawや属性の使い方

この手順でカスタムビューを追加するといろいろなデモコードが追加されてややこしくなってしまうので一旦必要最低限のコードまで省略したのが以下になります。カスタムビュークラスはViewを継承し、コードやXMLからビュー生成ができるようにするためのconstructorを定義します。


class MyCustomView : View {

    /** ①コードからのビュー作成用 */
    constructor(context: Context) : super(context) {
        init(null, 0)
    }

    /**
     * ②XMLからのビュー作成用(属性)
     * XML <com.example.MyCustomView ... /> と指定できるようにする
     */
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        init(attrs, 0)
    }

    /**
     * ③XMLからのビュー作成用(属性 + スタイル)
     * XMLで <com.example.MyCustomView style="@style/MyStyle" ... /> と指定できるようにする
     */
    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(
        context,
        attrs,
        defStyle
    ) {
        init(attrs, defStyle)
    }

    /** 基本の初期化メソッド */
    private fun init(attrs: AttributeSet?, defStyle: Int) { }

    /** ビューが画面に描画される際に呼び出されるメソッド */
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
    }
}

ついでに自動生成するとカスタムビューのプレビュー用のレイアウトファイルsample_my_custom_viewも自動生成してくれます。<com.example.MyCustomView ... />でビューを参照できていることを確認できます。


<FrameLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.XXXXXX.MyCustomView
        android:background="@color/black"
        android:layout_width="300dp"
        android:layout_height="300dp" />

</FrameLayout>

onDrawメソッド

onDrawビューが画面に描画される際に呼び出されるメソッドです。引数のCanvasオブジェクトを使用してビュー上に図形やテキスト、画像などを描画することが可能です。例えばテキストを追加で描画したい場合はandroid.graphics.Paint描画する際のスタイルやフォーマットを指定し、drawTextで描画します。


private val paint = Paint()

override fun onDraw(canvas: Canvas) {
    paint.color = Color.DKGRAY
    paint.textSize = 100f
    canvas.drawText("Hello World" , 10f, 100f, paint)
    super.onDraw(canvas)
}

onTouchEventメソッド

onTouchEventビューがタッチされた際に呼び出されるメソッドです。引数のMotionEventオブジェクトからイベントの種類を識別できます。


/** ビューがタッチされた際に呼び出されるメソッド */
override fun onTouchEvent(event: MotionEvent?): Boolean {
    if (event?.action == MotionEvent.ACTION_DOWN) {
        Toast.makeText(context, "タッチしたよ", Toast.LENGTH_LONG).show()
        return true
    }
    return super.onTouchEvent(event)
}

onMeasureメソッド

onMeasureビューのサイズを定義するためのメソッドです。setMeasuredDimensionを使用してビューのサイズを設定します。


/** ビューのサイズを定義するメソッド */
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    // 例:固定で200 * 100サイズにする
    val width = 200
    val height = 100
    // サイズを設定
    setMeasuredDimension(width, height)
}

widthMeasureSpec/heightMeasureSpec

引数から受け取れるwidthMeasureSpec/heightMeasureSpecではモード情報とサイズ情報が取得することができます。モードとサイズの取得にはgetMode/getSizeを使用します。


override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val widthMode = MeasureSpec.getMode(widthMeasureSpec)
    val widthSize = MeasureSpec.getSize(widthMeasureSpec)
    val heightMode = MeasureSpec.getMode(heightMeasureSpec)
    val heightSize = MeasureSpec.getSize(heightMeasureSpec)
}

取得できるモードは以下の3種類です。

公式リファレンス:MeasureSpec

実装例

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val desiredWidth = 200
    val desiredHeight = 100

    val widthMode = MeasureSpec.getMode(widthMeasureSpec)
    val widthSize = MeasureSpec.getSize(widthMeasureSpec)
    val heightMode = MeasureSpec.getMode(heightMeasureSpec)
    val heightSize = MeasureSpec.getSize(heightMeasureSpec)

    // 幅の測定
    val width: Int = when (widthMode) {
        // 親からのサイズに従う
        MeasureSpec.EXACTLY -> widthSize 
        // 最大サイズに制限
        MeasureSpec.AT_MOST -> minOf(desiredWidth, widthSize)
        // 制限なし
        MeasureSpec.UNSPECIFIED -> desiredWidth 
        else -> desiredWidth
    }

    // 高さの測定
    val height: Int = when (heightMode) {
        // 親からのサイズに従う
        MeasureSpec.EXACTLY -> heightSize
        // 最大サイズに制限
        MeasureSpec.AT_MOST -> minOf(desiredHeight, heightSize) 
        // 制限なし
        MeasureSpec.UNSPECIFIED -> desiredHeight 
        else -> desiredHeight
    }

    // 測定されたサイズを設定
    setMeasuredDimension(width, height)
}

invalidateメソッド

ビューを明示的に再描画させる(onDrawを再度呼び出す)にはinvalidateメソッドを使用します。invalidate自体は現在のビューを無効にするメソッドで、UIスレッドで呼び出す必要があります。

例えばテキストを入れ替える機能を実装する場合は以下のようにchangeTextの最後でinvalidateを呼び出します。記述し忘れるとビューが更新されません。


private val paint = Paint()
private var text = "Hello World"

override fun onDraw(canvas: Canvas) {
    paint.color = Color.DKGRAY
    paint.textSize = 100f
    canvas.drawText(text , 10f, 100f, paint)
    super.onDraw(canvas)
}

public fun changeText(text: String) {
    this.text = text
    invalidate() // 再描画が始まる
}

カスタム属性を定義する

カスタムビューではXML側から属性を指定することでビューの外観や動作をコントロールできるようにカスタム属性を定義することが可能です。カスタム属性を定義するにはリソースに<declare-styleable>を追加します。

attrタグを追加してname属性名formatデータ型を指定します。


<resources>
    <declare-styleable name="MyCustomView">
        <attr name="exampleString" format="string" />
        <attr name="exampleDimension" format="dimension" />
        <attr name="exampleColor" format="color" />
        <attr name="exampleDrawable" format="color|reference" />
    </declare-styleable>
</resources>

カスタム属性をXML側で利用できるようにするために名前空間(name space)を明示的に指定する必要があるので注意してください。名前空間xmlns:app="http://schemas.android.com/apk/res-auto"を追加してください。xmlns:XXXXXXは別になんでも良いです。


<FrameLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
+   xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
  
      <com.example.XXXXXX.MyCustomView
        android:layout_width="300dp"
        android:layout_height="300dp"
        app:exampleDimension="24sp"
        app:exampleDrawable="@android:drawable/ic_menu_add"
        app:exampleString="Hello, MyCustomView" />

コードからカスタム属性の値を取得する

XMLで指定したカスタム属性の値をコードから参照してみたいと思います。参照するためにはobtainStyledAttributesメソッドでまず属性リストを取得します。そこからデータ型に応じたメソッドで取得したい属性名を指定することで各値を取得することができます。

// 属性を読み込む
val a: TypedArray = context.obtainStyledAttributes(
    attrs, R.styleable.MyCustomView, defStyle, 0
)

// XMLから指定した値
val text = a.getString(
    R.styleable.MyCustomView_exampleString
)

// XMLから指定した値
val color = a.getColor(
    R.styleable.MyCustomView_exampleColor,
    Color.RED
)

// XMLから指定した値
val dimension = a.getDimension(
    R.styleable.MyCustomView_exampleDimension,
    0f
)
// hasValue:値があるかどうか
if (a.hasValue(R.styleable.MyCustomView_exampleDrawable)) {
    val exampleDrawable = a.getDrawable(
        R.styleable.MyCustomView_exampleDrawable
    )
    exampleDrawable?.callback = this
}
// 最後に必ずリサイクルする
a.recycle()

最後に必ずrecycleメソッドを呼び出してリサイクルします。

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index