You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
137 lines
4.8 KiB
Kotlin
137 lines
4.8 KiB
Kotlin
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
package org.mozilla.fenix.browser
|
|
|
|
import android.content.Context
|
|
import android.graphics.PointF
|
|
import android.util.AttributeSet
|
|
import android.view.GestureDetector
|
|
import android.view.MotionEvent
|
|
import android.widget.FrameLayout
|
|
import androidx.core.view.GestureDetectorCompat
|
|
|
|
/**
|
|
* Interface that allows intercepting and handling swipe gestures received in a [SwipeGestureLayout].
|
|
*/
|
|
interface SwipeGestureListener {
|
|
|
|
/**
|
|
* Called when the [SwipeGestureLayout] detects the start of a swipe gesture. The listener
|
|
* should return true if it wants to handle the swipe gesture. If the listener returns false
|
|
* it will not receive any callbacks for future events that the swipe produces.
|
|
*
|
|
* @param start the initial point where the gesture started
|
|
* @param next the next point in the gesture
|
|
*/
|
|
fun onSwipeStarted(start: PointF, next: PointF): Boolean
|
|
|
|
/**
|
|
* Called when the swipe gesture receives a new event.
|
|
*
|
|
* @param distanceX the change along the x-axis since the last swipe update
|
|
* @param distanceY the change along the y-axis since the last swipe update
|
|
*/
|
|
fun onSwipeUpdate(distanceX: Float, distanceY: Float)
|
|
|
|
/**
|
|
* Called when the user finishes the swipe gesture (ie lifts their finger off the screen)
|
|
*
|
|
* @param velocityX the velocity of the swipe along the x-axis
|
|
* @param velocityY the velocity of the swipe along the y-axis
|
|
*/
|
|
fun onSwipeFinished(velocityX: Float, velocityY: Float)
|
|
}
|
|
|
|
/**
|
|
* A [FrameLayout] that allows listeners to intercept and handle swipe events.
|
|
*
|
|
* Listeners are called in the order they are added and the first listener to intercept a swipe event
|
|
* is the only listener that will receive events for the duration of that swipe.
|
|
*/
|
|
class SwipeGestureLayout @JvmOverloads constructor(
|
|
context: Context,
|
|
attrs: AttributeSet? = null,
|
|
defStyleAttr: Int = 0
|
|
) : FrameLayout(context, attrs, defStyleAttr) {
|
|
|
|
private val gestureListener = object : GestureDetector.SimpleOnGestureListener() {
|
|
override fun onDown(e: MotionEvent?): Boolean {
|
|
return true
|
|
}
|
|
|
|
override fun onScroll(
|
|
e1: MotionEvent?,
|
|
e2: MotionEvent?,
|
|
distanceX: Float,
|
|
distanceY: Float
|
|
): Boolean {
|
|
val start = e1?.let { event -> PointF(event.rawX, event.rawY) } ?: return false
|
|
val next = e2?.let { event -> PointF(event.rawX, event.rawY) } ?: return false
|
|
|
|
if (activeListener == null && !handledInitialScroll) {
|
|
activeListener = listeners.firstOrNull { listener ->
|
|
listener.onSwipeStarted(start, next)
|
|
}
|
|
handledInitialScroll = true
|
|
}
|
|
activeListener?.onSwipeUpdate(distanceX, distanceY)
|
|
return activeListener != null
|
|
}
|
|
|
|
override fun onFling(
|
|
e1: MotionEvent?,
|
|
e2: MotionEvent?,
|
|
velocityX: Float,
|
|
velocityY: Float
|
|
): Boolean {
|
|
activeListener?.onSwipeFinished(velocityX, velocityY)
|
|
return if (activeListener != null) {
|
|
activeListener = null
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
private val gestureDetector = GestureDetectorCompat(context, gestureListener)
|
|
|
|
private val listeners = mutableListOf<SwipeGestureListener>()
|
|
private var activeListener: SwipeGestureListener? = null
|
|
private var handledInitialScroll = false
|
|
|
|
fun addGestureListener(listener: SwipeGestureListener) {
|
|
listeners.add(listener)
|
|
}
|
|
|
|
override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
|
|
return when (event?.actionMasked) {
|
|
MotionEvent.ACTION_DOWN -> {
|
|
handledInitialScroll = false
|
|
gestureDetector.onTouchEvent(event)
|
|
false
|
|
}
|
|
else -> gestureDetector.onTouchEvent(event)
|
|
}
|
|
}
|
|
|
|
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
|
return when (event?.actionMasked) {
|
|
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
|
|
gestureDetector.onTouchEvent(event)
|
|
// If the active listener is not null here, then we haven't detected a fling
|
|
// so notify the listener that the swipe was finished with 0 velocity
|
|
activeListener?.onSwipeFinished(
|
|
velocityX = 0f,
|
|
velocityY = 0f
|
|
)
|
|
activeListener = null
|
|
false
|
|
}
|
|
else -> gestureDetector.onTouchEvent(event)
|
|
}
|
|
}
|
|
}
|