blob: cf0cefec401aa83f083845ef0652c44fa5669cad [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.bubbles
import android.graphics.PointF
import android.os.Handler
import android.os.Looper
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import android.view.ViewConfiguration
import kotlin.math.hypot
/**
* Listener which receives [onDown], [onMove], and [onUp] events, with relevant information about
* the coordinates of the touch and the view relative to the initial ACTION_DOWN event and the
* view's initial position.
*/
abstract class RelativeTouchListener : View.OnTouchListener {
/**
* Called when an ACTION_DOWN event is received for the given view.
*
* @return False if the object is not interested in MotionEvents at this time, or true if we
* should consume this event and subsequent events, and begin calling [onMove].
*/
abstract fun onDown(v: View, ev: MotionEvent): Boolean
/**
* Called when an ACTION_MOVE event is received for the given view. This signals that the view
* is being dragged.
*
* @param viewInitialX The view's translationX value when this touch gesture started.
* @param viewInitialY The view's translationY value when this touch gesture started.
* @param dx Horizontal distance covered since the initial ACTION_DOWN event, in pixels.
* @param dy Vertical distance covered since the initial ACTION_DOWN event, in pixels.
*/
abstract fun onMove(
v: View,
ev: MotionEvent,
viewInitialX: Float,
viewInitialY: Float,
dx: Float,
dy: Float
)
/**
* Called when an ACTION_UP event is received for the given view. This signals that a drag or
* fling gesture has completed.
*
* @param viewInitialX The view's translationX value when this touch gesture started.
* @param viewInitialY The view's translationY value when this touch gesture started.
* @param dx Horizontal distance covered, in pixels.
* @param dy Vertical distance covered, in pixels.
* @param velX The final horizontal velocity of the gesture, in pixels/second.
* @param velY The final vertical velocity of the gesture, in pixels/second.
*/
abstract fun onUp(
v: View,
ev: MotionEvent,
viewInitialX: Float,
viewInitialY: Float,
dx: Float,
dy: Float,
velX: Float,
velY: Float
)
/** The raw coordinates of the last ACTION_DOWN event. */
private val touchDown = PointF()
/** The coordinates of the view, at the time of the last ACTION_DOWN event. */
private val viewPositionOnTouchDown = PointF()
private val velocityTracker = VelocityTracker.obtain()
private var touchSlop: Int = -1
private var movedEnough = false
private var performedLongClick = false
@Suppress("UNCHECKED_CAST")
override fun onTouch(v: View, ev: MotionEvent): Boolean {
addMovement(ev)
val dx = ev.rawX - touchDown.x
val dy = ev.rawY - touchDown.y
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
if (!onDown(v, ev)) {
return false
}
// Grab the touch slop, it might have changed if the config changed since the
// last gesture.
touchSlop = ViewConfiguration.get(v.context).scaledTouchSlop
touchDown.set(ev.rawX, ev.rawY)
viewPositionOnTouchDown.set(v.translationX, v.translationY)
performedLongClick = false
v.handler.postDelayed({
if (v.isLongClickable) {
performedLongClick = v.performLongClick()
}
}, ViewConfiguration.getLongPressTimeout().toLong())
}
MotionEvent.ACTION_MOVE -> {
if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) {
movedEnough = true
v.handler.removeCallbacksAndMessages(null)
}
if (movedEnough) {
onMove(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy)
}
}
MotionEvent.ACTION_UP -> {
if (movedEnough) {
velocityTracker.computeCurrentVelocity(1000 /* units */)
onUp(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy,
velocityTracker.xVelocity, velocityTracker.yVelocity)
} else if (!performedLongClick) {
v.performClick()
} else {
v.handler.removeCallbacksAndMessages(null)
}
velocityTracker.clear()
movedEnough = false
}
}
return true
}
/**
* Adds a movement to the velocity tracker using raw screen coordinates.
*/
private fun addMovement(event: MotionEvent) {
val deltaX = event.rawX - event.x
val deltaY = event.rawY - event.y
event.offsetLocation(deltaX, deltaY)
velocityTracker.addMovement(event)
event.offsetLocation(-deltaX, -deltaY)
}
}