DetectDragModifier now detects long-presses
The long press initiates an item drag; otherwise the view just scrolls on drag.
This commit is contained in:
parent
032a853072
commit
f2a1a638db
@ -1,37 +1,75 @@
|
||||
package org.schabi.newpipe.ui
|
||||
|
||||
import android.view.MotionEvent
|
||||
import androidx.compose.foundation.gestures.awaitEachGesture
|
||||
import androidx.compose.foundation.gestures.awaitFirstDown
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException
|
||||
import androidx.compose.ui.input.pointer.changedToUp
|
||||
import androidx.compose.ui.input.pointer.isOutOfBounds
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.input.pointer.positionChange
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.util.fastAll
|
||||
import androidx.compose.ui.util.fastAny
|
||||
|
||||
/**
|
||||
* Detects a drag gesture **without** trying to filter out any misclicks. This is useful in menus
|
||||
* where items are dragged around, where the usual misclick guardrails would cause unexpected lags
|
||||
* or strange behaviors when dragging stuff around quickly. For other use cases, use
|
||||
* [androidx.compose.foundation.gestures.detectDragGestures] or
|
||||
* or strange behaviors when dragging stuff around quickly. Also detects whether a drag gesture
|
||||
* began with a long press or not, which can be useful to decide whether an item should be dragged
|
||||
* around (in case of long-press) or the view should be scrolled (otherwise). For other use cases,
|
||||
* use [androidx.compose.foundation.gestures.detectDragGestures] or
|
||||
* [androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress].
|
||||
*
|
||||
* @param beginDragGesture called when the user first touches the screen (down event) with the
|
||||
* pointer position.
|
||||
* pointer position and whether a long press was detected.
|
||||
* @param handleDragGestureChange called with the current pointer position and the difference from
|
||||
* the last position, every time the user moves the finger after [beginDragGesture] has been called.
|
||||
* @param endDragGesture called when the drag gesture finishes, after [beginDragGesture] has been
|
||||
* called.
|
||||
*/
|
||||
fun Modifier.detectDragGestures(
|
||||
beginDragGesture: (position: IntOffset) -> Unit,
|
||||
beginDragGesture: (position: IntOffset, wasLongPressed: Boolean) -> Unit,
|
||||
handleDragGestureChange: (position: IntOffset, positionChange: Offset) -> Unit,
|
||||
endDragGesture: () -> Unit
|
||||
): Modifier {
|
||||
return this.pointerInput(Unit) {
|
||||
awaitEachGesture {
|
||||
val down = awaitFirstDown()
|
||||
val wasLongPressed = try {
|
||||
// code in this branch was taken from AwaitPointerEventScope.waitForLongPress(),
|
||||
// which unfortunately is private
|
||||
withTimeout(viewConfiguration.longPressTimeoutMillis) {
|
||||
while (true) {
|
||||
val event = awaitPointerEvent()
|
||||
if (event.changes.fastAll { it.changedToUp() }) {
|
||||
// All pointers are up
|
||||
break
|
||||
}
|
||||
|
||||
if (event.classification == MotionEvent.CLASSIFICATION_DEEP_PRESS) {
|
||||
return@withTimeout true
|
||||
}
|
||||
|
||||
if (
|
||||
event.changes.fastAny {
|
||||
it.isConsumed || it.isOutOfBounds(IntSize(0, 0), extendedTouchPadding)
|
||||
}
|
||||
) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return@withTimeout false
|
||||
}
|
||||
} catch (_: PointerEventTimeoutCancellationException) {
|
||||
true
|
||||
}
|
||||
|
||||
val pointerId = down.id
|
||||
beginDragGesture(down.position.toIntOffset())
|
||||
beginDragGesture(down.position.toIntOffset(), wasLongPressed)
|
||||
while (true) {
|
||||
val change = awaitPointerEvent().changes.find { it.id == pointerId }
|
||||
if (change == null || !change.pressed) {
|
||||
|
||||
@ -94,7 +94,7 @@ import kotlin.math.floor
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
const val TAG = "LongPressMenuEditor"
|
||||
internal const val TAG = "LongPressMenuEditor"
|
||||
|
||||
// TODO padding doesn't seem to work as expected when the list becomes scrollable?
|
||||
@Composable
|
||||
@ -166,7 +166,12 @@ fun LongPressMenuEditor(modifier: Modifier = Modifier) {
|
||||
}
|
||||
|
||||
// this beginDragGesture() overload is only called when moving the finger (not on DPAD's Enter)
|
||||
fun beginDragGesture(pos: IntOffset) {
|
||||
fun beginDragGesture(pos: IntOffset, wasLongPressed: Boolean) {
|
||||
if (!wasLongPressed) {
|
||||
// items can be dragged around only if they are long-pressed;
|
||||
// use the drag as scroll otherwise
|
||||
return
|
||||
}
|
||||
val rawItem = findItemForOffsetOrClosestInRow(pos) ?: return
|
||||
beginDragGesture(pos, rawItem)
|
||||
autoScrollSpeed = 0f
|
||||
@ -229,7 +234,8 @@ fun LongPressMenuEditor(modifier: Modifier = Modifier) {
|
||||
fun handleDragGestureChange(pos: IntOffset, posChangeForScrolling: Offset) {
|
||||
val dragItem = activeDragItem
|
||||
if (dragItem == null) {
|
||||
// when the user clicks outside of any draggable item, let the list be scrolled
|
||||
// when the user clicks outside of any draggable item, or if the user did not long-press
|
||||
// on an item to begin with, let the list be scrolled
|
||||
gridState.dispatchRawDelta(-posChangeForScrolling.y)
|
||||
return
|
||||
}
|
||||
|
||||
@ -904,7 +904,7 @@
|
||||
<string name="popup_from_here">Popup\nfrom here</string>
|
||||
<string name="play_from_here">Play\nfrom here</string>
|
||||
<string name="long_press_menu_enabled_actions">Enabled actions:</string>
|
||||
<string name="long_press_menu_enabled_actions_description">Reorder the actions by dragging them around</string>
|
||||
<string name="long_press_menu_enabled_actions_description">Reorder the actions by long pressing and then dragging them around</string>
|
||||
<string name="long_press_menu_hidden_actions">Hidden actions:</string>
|
||||
<string name="long_press_menu_hidden_actions_description">Drag the header or the actions to this section to hide them</string>
|
||||
<string name="header">Header</string>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user