タッチ操作でViewなどを動かす場合、単にドラッグに合わせて動かすだけでなく、指を離す前の移動速度を計算し、慣性動作をさせると操作性が上がりますね。
AndroidではVelocityTrackerを使うことで、操作の速度を簡単に計算することができます。
ざっくり言うと以下のように使います。
private var velocityTracker: VelocityTracker? = null
override fun onTouchEvent(event: MotionEvent): Boolean {
val velocityTracker = velocityTracker ?: VelocityTracker.obtain().also { velocityTracker = it }
when (event.action) {
MotionEvent.ACTION_DOWN -> {
velocityTracker.clear()
velocityTracker.addMovement(event)
}
MotionEvent.ACTION_MOVE -> {
velocityTracker.addMovement(event)
}
MotionEvent.ACTION_UP -> {
// 引数はms、1000を渡すとpixel/sの速度が得られる
velocityTracker.computeCurrentVelocity(1000)
val vx = velocityTracker.xVelocity
val vy = velocityTracker.yVelocity
// ここで計算した速度を使って慣性動作させる
velocityTracker.recycle()
this.velocityTracker = null
}
MotionEvent.ACTION_CANCEL -> {
velocityTracker.recycle()
this.velocityTracker = null
}
}
return true
}
View自体が動いている場合はオフセット計算が必要
しかし、View自体が動いている場合は、オフセット計算が必要です。
私は、毎回オフセット計算を忘れて時間を無駄にしております。頻繁に使うものでもないですしね。。
ViewのonTouchEventやOnTouchListenerのonTouchに渡されるMotionEventの座標は、そのViewの左上を原点とする相対座標系に変換されています。VelocityTrackerはこの相対的な座標を元に速度を計算しますので、このタッチ操作によって移動しているViewのMotionEventを渡してしまうと、操作の移動速度ではなく、タッチとViewの移動のズレだけを拾うことになるので、おかしな値が計算されてしまいます。
というわけで、親Viewの座標系に変換してVelocityTrackerに渡すか
private fun VelocityTracker.addAbsoluteMovement(v: View, event: MotionEvent) {
val dx = v.x
val dy = v.y
event.offsetLocation(dx, dy)
addMovement(event)
event.offsetLocation(-dx, -dy)
}
raw情報を元に、絶対座標に変換してVelocityTrackerに渡す必要があります。
private fun VelocityTracker.addAbsoluteMovement(event: MotionEvent) {
val dx = event.rawX - event.x
val dy = event.rawY - event.y
event.offsetLocation(dx, dy)
addMovement(event)
event.offsetLocation(-dx, -dy)
}
注意点としては、offsetLocationで座標系を変換した場合、必ず逆変換を行って元に戻しておきましょう。
以上です。