Android
Kotlin

[Android]独自レイアウトを作成する

概要

今回、Androidで独自レイアウトを作成する際にハマったこと、役に立ったことなどをまとめます。

手順

  1. 独自レイアウトのクラスを作成する
  2. onMeasure(), onLayout()を実装する
  3. onInterceptTouchEvent(), onTouchEvent()を実装する(※タッチイベントを制御する場合)
  4. VelocityTrackerを実装する(※加速度を取得する場合)

独自レイアウトクラスの作成

まずは、独自クラスを作成します。今回はFrameLayoutのサブクラスを作成していきます。

class MyLayout(context: Context, attrs: AttributeSet, @AttrRes defStyle: Int, @StyleRes defStyleRes: Int) : FrameLayout(context, attrs, defStyle, defStyleRes) {

  constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0)
  constructor(context: Context, attrs: AttributeSet, defStyle: Int) : this(context, attrs, defStyle, 0)

 /* その他の実装 (後述) */

}

このクラスを作成することで、XMLに記述をすることが可能になります。

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

  <!-- 子Viewを追加します -->

</foo.bar.MyLayout>

さて、次にonMeasure(), onLayout()を実装します。

onMeasure(), onLayout()を実装する

実装の詳細は別記事に譲るとして、2つのメソッドを簡単に説明します。
onMeasure, onLayoutで子Viewに対してのWidth,Heightの設定と位置の指定が別れているのがわかりにくい(iOSから来た人は)ので注意してください。

onMeasure

onMeasureでやることは2つです。

  • 子Viewに適切なサイズに設定する
  • 自分自身のサイズを設定する

onLayout

  • 子Viewのレイアウトをします

onInterceptTouchEvent(), onTouchEvent()を実装する

Viewでタッチイベントを制御したい場合は、上記メソッドを実装します。
TouchEventと言っていますが、MotionEvent.ACTION_MOVEなど、指を移動させたイベントも含まれているので注意です。(少しハマりました)

  • onInterceptTouchEvent(): MotionEventを子に伝搬するかどうか
    • 子Viewに伝搬する場合は false を返す
  • onTouchEvent(): MotionEventをハンドルする
    • 子Viewに伝搬する場合は false を返す

以下に、onInterceptTouchEvent()の実装サンプルを書いておきます。onTouchEvent()も同じように実装できるので、割愛します。例えば、スワイプ量を取得するには以下のように実装すれば可能です。それも合わせて書きます。
また、独自のフラグを操作して、MotionEventを伝搬する・しないをハンドルすることができます。

private var lastStartX: Float = -1.0F
private var lastStartY: Float = -1.0F

override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
  if (ev == null) {
    return false
  }

  when (ev.action) {
    MotionEvent.ACTION_DOWN -> {
      /* 押された */

      // 初期位置を保存
      lastStartX = ev.rawX
      lastStartY = ev.rawY
    }
    MotionEvent.ACTION_UP -> {
      /* 離した */
    }
    MotionEvent.ACTION_CANCEL -> {
      /* キャンセルされた */
    }
    MotionEvent.ACTION_MOVE -> {
      /* 移動した */

      // スワイプ量
      val swipeX = abs(ev.rawX - lastStartX)
      val swipeY = abs(ev.rawY - lastStartY)
    }
  }

  return false
}

VelocityTrackerを実装する

Layout内でVelocity(加速度)を取得する際には VelocityTrackerを実装します。

具体的には...

override fun onEvent(ev: MotionEvent?): Boolean {
  if (ev == null) {
    return false
  }

  when (ev.action) {
    MotionEvent.ACTION_DOWN -> {
      // VelocityTrackerを初期状態にする
      tracker.clear()

      // VelocityTrackerにMotionEventを追加
      tracker.addMovement(ev)
    }
    MotionEvent.ACTION_MOVE -> {
      // VelocityTrackerにMotionEventを追加
      tracker.addMovement(ev)       

      // 加速度を取得する
      // (注意)加速度を取得する前に必ず`computeCurrentVelocity`を呼ぶ
      tracker.computeCurrentVelocity(500)
      val velocityX = tracker.xVelocity
      val velocityY = tracker.yVelocity
    }
  }

  return isVerticalDragging
}

終わりに

以上、簡単ではありましたが、独自Layoutを作成して子Viewをハンドリングするためのイベントを制御する方法をまとめました。

参考