CustomViewでメーターっぽいViewを作成する

続き?

iseabjapan.hatenablog.com

やる事

①コンストラクタを作成

②Viewクラスのメソッドをオーバーライド

- onMeasure()
- onTouchEvent()
- onDraw()

いざ

①コンストラクタを作成

CustomViewを作成する=Viewクラスを継承する際は4つのセカンダリコンストラクタを書く必要がある。ただし@JvmOverloads のアノテーションをプライマリコンストラクタに追記するとコンストラクタはその一つで済む。

具体的には

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

で十分となる。

 

②Viewクラスのメソッドをオーバーライド

onMeasure()

ここでやるべきは setMeasureDimension()でViewのサイズを確定させる事。 引数の二つのmeasurespecには親ビューからのサイズ情報が入っており、サイズ指定する場合はMeasureSpec.getSize()で引き出す必要がある。

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        //スクリーンサイズを取得する。
        val displayMetrics = DisplayMetrics()
        (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.getMetrics(
            displayMetrics
        )
        mScreenWidth = displayMetrics.widthPixels
        mScreenHeight = displayMetrics.heightPixels
        //サイズの指定が会った際に大きさを取得する。
        if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(widthMeasureSpec)) {
            mScreenWidth = MeasureSpec.getSize(widthMeasureSpec)
        }
        if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(heightMeasureSpec)) {
            mScreenHeight = MeasureSpec.getSize(heightMeasureSpec)
        }
        //自分自身のサイズを setMeasuredDimension で確定させる
        setMeasuredDimension(mScreenWidth, mScreenHeight)
        initButtonSize(mScreenWidth, mScreenHeight) ※ 一つ一つのボタンの大きさを確定
    }

initButtonSize()のなかで各ボタンのサイズを確定させてこんなデータクラスのリストに突っ込んでいる。

    data class ExtractLevel(
        var levelIndex: Int = 0,
        var position: RectF
    )
onTouchEvent()

ここでやるべきはタッチされた際の挙動を制御する事。ここのロジック部分のコードは省略するが やってる事は移動(MotionEvent.ACTION_MOVE)が呼ばれた際に、方向(上下)を判別しinvalidate()による"onDraw()""の呼び出しである。

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        val currentTouchId = getTouchedLevelIndex(x, y) //現在タップされたレベル位置
            MotionEvent.ACTION_MOVE -> {
    〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
                if (mCurrentLevel != currentTouchId && currentTouchId != -1) {
                    updateLevelState(currentTouchId)  //現在のタッチされているボタンのレベルを更新
                    invalidate() //再描画処理
                }
    〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
            }
    }

※厳密には移動距離、移動方向の判別も行っているが気になる人はgitで確認して欲しい。

onDraw()

ここでやるべきはViewの描画処理。今回はonMeasure()内で確定させた各ボタンをCanvasクラスdrawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint)で描画している。

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

    private fun drawLevels(canvas: Canvas) { 
        mExtractLevels.forEach { 
            val paint = Paint()
            paint.style = Paint.Style.FILL
            paint.color = ContextCompat.getColor(
                context,
                if (isLevelSelected(it.levelIndex)) R.color.colorLevel else R.color.emptyColorLevel
            )
            canvas.drawRoundRect(
                it.position,
                connerRadius.toFloat(),
                connerRadius.toFloat(),
                paint
            )
        }
    }

完成

 f:id:iseAB:20191029221133g:plain

全部で3回位を想定していたが実質1回で終了してしまった。。。

https://developer.android.com/reference/android/view/View.html#public-constructors_1 http://tiro105.hateblo.jp/entry/2014/06/19/154405