10
10

More than 3 years have passed since last update.

[Android]カスタムビューの作り方 (フルカスタムコンポーネント)

Last updated at Posted at 2019-12-30

はじめに

Android では公式にあるとおりカスタムビューを作成する方法が何パターンかあります。
今回はフルカスタムコンポーネントを作成してみたので作成方法をまとめたいと思います。

名称 説明
フルカスタムコンポーネント Viewを継承していちから新たなコンポーネントを作成するパターン
複合コンポーネント 既存のコンポーネントを複数組み合わせて新たなコンポーネントを作成するパターン

mermaid.png

今回はnumberdisplayModeを設定すると、モードごとに数字を表示する次のようなビューを作ろうと思います。

Screenshot_1577611733.png Screenshot_1577612085.png

1. 独自のViewクラスを継承する

フルカスタムコンポーネントを作成するために、まずViewを継承したクラスを実装します。

NumberView.kt
class NumberView(context : Context, attributeSet: AttributeSet) : View(context, attributeSet)

このようにViewを継承したクラスを定義するとレイアウトでCustomViewを記述できるようになります。

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

    <kaleidot725.customviewsample.NumberView
        android:id="@+id/number_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:number="1234567890"
        app:displayMode="japanese"/>

</FrameLayout>

2. 独自のAttributesを宣言する

フルカスタムコンポーネントで独自のAttriubtesを利用するには、
valuesattrs.xmlを作成し、独自のAttributesを宣言する必要があります。
次のようにattrs.xmlを記述すると、レイアウトでAttributesで利用できるようになります。

attrs.xml
<resources>
    <declare-styleable name="NumberView">
        <attr name="number" format="integer"/>
        <attr name="displayMode" format="enum">
            <enum name="number" value="0"/>
            <enum name="english" value="1"/>
            <enum name="japanese" value="2"/>
        </attr>
    </declare-styleable>
</resources>
main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

    <kaleidot725.customviewsample.NumberView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:number="1" 
        app:displayMode="number"/>

</androidx.constraintlayout.widget.ConstraintLayout>

3. 独自のAttributesを取得する

obtainStyledAttributesを利用すれば、独自に宣言したattributesを取得できます。
attributesを宣言するとR.styleable.<CustomView名称>_<attributes名称>を指定できるようになっているので、
次のようにそのIDobtainStyledAttributesに指定してattributesを取得します。

NumberView.kt
    private var number : Int = 0
    private var displayMode : Int = 0

    init {
        context.theme.obtainStyledAttributes(attributeSet, R.styleable.NumberView, 0, 0).apply {
            try {
                number = getInteger(R.styleable.NumberView_number, 0)
                displayMode = getInteger(R.styleable.NumberView_displayMode, 0)
            } finally {
                recycle()
            }
        }
    }

4. onDrawに描画処理を記述する

View.onDrawをオーバーライドすることでViewの描画内容を変更できます。
Viewの描画にはCanvasを利用します、Canvasでどのような描画ができるかは次に詳しくまとまっています。

今回はCanvasdrawTextで文字の描画、drawRectで背景の描画を実装してみます。
また文字の描画ではnumberdisplayModeに応じて描画する文字列を変換するようにしてみます。

NumberView.kt
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        val backgroundPaint = Paint().apply {
            setColor(Color.GREEN)
        }
        canvas?.drawRect(0f, 0f, this.width.toFloat(), this.height.toFloat(), backgroundPaint)

        val textPaint = Paint().apply {
            setColor(Color.BLACK)
            textSize = 50f
        }
        val text = getDisplayNumber(number, displayMode)
        val textWidth = getTextWidth(text, textPaint)
        canvas?.drawText(text, (this.width / 2f) - (textWidth / 2), this.height / 2f, textPaint)
    }

    private fun getTextWidth(text : String, paint : Paint) : Float {
        val bounds = Rect()
        paint.getTextBounds(text, 0, text.length, bounds)
        return bounds.width().toFloat() + bounds.left.toFloat()
    }

    private fun getDisplayNumber(number : Int, displayMode : Int) : String {
        fun String.replace(vararg pairs: Pair<String, String>): String =
            pairs.fold(this) { acc, (old, new) -> acc.replace(old, new, ignoreCase = true) }

        return when(displayMode) {
            0 -> { number.toString() }
            1 -> { number.toString().replace(
                    "0" to "〇", "1" to "一", "2" to "二", "3" to "三", "4" to "四",
                    "5" to "五", "6" to "六", "7" to "七", "8" to "八", "9" to "九"
                )
            }
            else -> { "error" }
        }
    }

すると指定したNumberJapanseに変換されて表示されるようになります。

Screenshot_1577611733.png

5. 独自のAttributesを変更できるようにする

最後に独自のAttributesを変更できるようにsetNumbersetDisplayModeメソッドを用意します。
Attributesの変更を反映させるために変更後はinvalidaterequestLayoutを呼ぶ必要があります。

NumberView.kt
    private var number : Int = 0
    private var displayMode : Int = 0

    fun setNumber(number : Int) {
        this.number = number
        this.invalidate()
        this.requestLayout()
    }

    fun setDisplayMode(displayMode: Int) {
        this.displayMode = displayMode
        this.invalidate()
        this.requestLayout()
    }

試しにNumberViewを取得して、setNumbersetDisplayModeを呼び出してみます。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val numberView : NumberView = findViewById(R.id.number_view)
        numberView.setNumber(999999)
        numberView.setDisplayMode(0)
    }
}

こんな感じで設定変更が適用され表示も変化するようになります。

Screenshot_1577612085.png

おわりに

今回作成したサンプルは次にまとめています。
なので必要に応じて閲覧していただければと思います。

参考文献

10
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
10