[go: nahoru, domu]

blob: 7a7b055560c353f1ff6e1ad6f5b6a8b46f051017 [file] [log] [blame]
/*
* Copyright 2019 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 androidx.ui.core.gesture
import androidx.compose.remember
import androidx.ui.core.Direction
import androidx.ui.core.Modifier
import androidx.ui.core.PointerEventPass
import androidx.ui.core.composed
import androidx.ui.core.gesture.scrollorientationlocking.Orientation
import androidx.ui.core.gesture.scrollorientationlocking.ScrollOrientationLocker
import androidx.ui.geometry.Offset
/**
* Defines the callbacks associated with scrolling.
*/
interface ScrollCallback {
/**
* Override to be notified when a scroll has started.
*
* This will be called as soon as the average distance of all pointers surpasses the touch slop
* in the relevant orientation.
*
* Only called if the last called if the most recent call among [onStart], [onStop], and
* [onCancel] was [onStop] or [onCancel].
*
* @param downPosition The pointer input position of the down event.
*/
fun onStart(downPosition: Offset) {}
/**
* Override to be notified when a distance has been scrolled.
*
* When overridden, return the amount of the [scrollDistance] that has been consumed.
*
* Called immediately after [onStart] and for every subsequent pointer movement, as long as the
* movement was enough to constitute a scroll (the average movement on in the relevant
* orientation is not equal to 0).
*
* Note: This may be called multiple times in a single pass and the values should be accumulated
* for each call.
*
* @param scrollDistance The distance that has been scrolled. Reflects the average scroll
* distance of all pointers.
*/
fun onScroll(scrollDistance: Float) = 0f
/**
* Override to be notified when a scroll has stopped.
*
* This is called once all pointers have released the associated PointerInputFilter.
*
* Only called if the last called if the most recent call among [onStart], [onStop], and
* [onCancel] was [onStart].
*
* @param velocity The velocity of the scroll in the relevant orientation at the point in time
* when all pointers have released the relevant PointerInputFilter. In pixels per second.
*/
fun onStop(velocity: Float) {}
/**
* Override to be notified when the scroll has been cancelled.
*
* This is called in response to a cancellation event such as the associated
* PointerInputFilter having been removed from the hierarchy.
*
* Only called if the last called if the most recent call among [onStart], [onStop], and
* [onCancel] was [onStart].
*/
fun onCancel() {}
}
/**
* Like [Modifier.dragGestureFilter], this gesture filter will detect dragging, but will only do
* so along the given [orientation].
*
* This gesture filter also disambiguates amongst other scrollGestureFilters such that for all
* pointers that this gesture filter uses to scroll in the given [orientation], other
* scrollGestureFilters (or other clients of [ScrollOrientationLocker]) will not use those same
* pointers to drag in the other [orientation]. Likewise, this scrollGestureFilter will not use
* pointers to drag if they are already being used to drag in a different orientation.
*
* Note: [canDrag] will only be queried in directions that exist within the given [orientation].
*
* Note: Changing the value of [orientation] will reset the gesture filter such that it will not
* respond to input until new pointers are detected.
*
* @param scrollCallback: The set of callbacks for scrolling.
* @param orientation: The orientation this gesture filter uses.
* @param canDrag Set to limit the types of directions under which touch slop can be exceeded.
* Return true if you want a drag to be started due to the touch slop being surpassed in the
* given [Direction]. If [canDrag] is not provided, touch slop will be able to be exceeded in all
* directions that are in the provided [orientation].
* @param startDragImmediately Set to true to have dragging begin immediately when a pointer is
* "down", preventing children from responding to the "down" change. Generally, this parameter
* should be set to true when the child of the GestureDetector is animating, such that when a finger
* touches it, dragging is immediately started so the animation stops and dragging can occur.
*/
// TODO(shepshapard): Consider breaking up ScrollCallback such that the onScroll lambda can be
// the final parameter.
fun Modifier.scrollGestureFilter(
scrollCallback: ScrollCallback,
orientation: Orientation,
canDrag: ((Direction) -> Boolean)? = null,
startDragImmediately: Boolean = false
): Modifier = composed {
val coordinator = remember { ScrollGestureFilterCoordinator() }
coordinator.scrollCallback = scrollCallback
coordinator.orientation = orientation
// TODO(b/146427920): There is a gap here where RawPressStartGestureDetector can cause a call to
// DragObserver.onStart but if the pointer doesn't move and releases, (or if cancel is called)
// The appropriate callbacks to DragObserver will not be called.
rawDragGestureFilter(
coordinator.rawDragObserver,
coordinator::enabledOrStarted,
orientation
)
.dragSlopExceededGestureFilter(coordinator::enableDrag, canDrag, orientation)
.rawPressStartGestureFilter(
coordinator::startDrag,
startDragImmediately,
PointerEventPass.InitialDown
)
}
/**
* Coordinates the logic of rawDragGestureFilter, dragSlopExceededGestureFilter, and
* rawPressStartGestureFilter.
*
* Also maps the output of rawDragGestureFilter to the output of scrollGestureFilter.
*/
private class ScrollGestureFilterCoordinator {
private var started = false
private var enabled = false
lateinit var scrollCallback: ScrollCallback
lateinit var orientation: Orientation
val enabledOrStarted
get() = started || enabled
fun enableDrag() {
enabled = true
}
fun startDrag(downPosition: Offset) {
started = true
scrollCallback.onStart(downPosition)
}
val rawDragObserver: DragObserver =
object : DragObserver {
override fun onStart(downPosition: Offset) {
if (!started) {
scrollCallback.onStart(downPosition)
}
}
override fun onDrag(dragDistance: Offset): Offset {
return when (orientation) {
Orientation.Horizontal -> Offset(scrollCallback.onScroll(dragDistance.x), 0f)
Orientation.Vertical -> Offset(0f, scrollCallback.onScroll(dragDistance.y))
}
}
override fun onStop(velocity: Offset) {
started = false
enabled = false
return when (orientation) {
Orientation.Horizontal -> scrollCallback.onStop(velocity.x)
Orientation.Vertical -> scrollCallback.onStop(velocity.y)
}
}
override fun onCancel() {
started = false
enabled = false
scrollCallback.onCancel()
}
}
}