概要
今回、Androidで独自レイアウトを作成する際にハマったこと、役に立ったことなどをまとめます。
手順
- 独自レイアウトのクラスを作成する
-
onMeasure()
,onLayout()
を実装する -
onInterceptTouchEvent()
,onTouchEvent()
を実装する(※タッチイベントを制御する場合) -
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
を返す
- 子Viewに伝搬する場合は
- onTouchEvent(): MotionEventをハンドルする
- 子Viewに伝搬する場合は
false
を返す
- 子Viewに伝搬する場合は
以下に、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をハンドリングするためのイベントを制御する方法をまとめました。