Skip to content

Commit b7e256a

Browse files
Extract drag detection logic to interface (#71)
* Extract drag detection logic to DragGestureDetector interface * Rename DragGestureDetector.Normal to DragGestureDetector.Press * Update dragGestureDetector param position in docs * format file --------- Co-authored-by: Calvin Liang <me@calvin.sh>
1 parent 7b81920 commit b7e256a

File tree

4 files changed

+76
-138
lines changed

4 files changed

+76
-138
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package sh.calvin.reorderable
2+
3+
import androidx.compose.foundation.gestures.detectDragGestures
4+
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
5+
import androidx.compose.ui.geometry.Offset
6+
import androidx.compose.ui.input.pointer.PointerInputChange
7+
import androidx.compose.ui.input.pointer.PointerInputScope
8+
9+
fun interface DragGestureDetector {
10+
suspend fun PointerInputScope.detect(
11+
onDragStart: (Offset) -> Unit,
12+
onDragEnd: () -> Unit,
13+
onDragCancel: () -> Unit,
14+
onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit
15+
)
16+
17+
object Press : DragGestureDetector {
18+
override suspend fun PointerInputScope.detect(
19+
onDragStart: (Offset) -> Unit,
20+
onDragEnd: () -> Unit,
21+
onDragCancel: () -> Unit,
22+
onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit
23+
) {
24+
detectDragGestures(onDragStart, onDragEnd, onDragCancel, onDrag)
25+
}
26+
}
27+
28+
object LongPress : DragGestureDetector {
29+
override suspend fun PointerInputScope.detect(
30+
onDragStart: (Offset) -> Unit,
31+
onDragEnd: () -> Unit,
32+
onDragCancel: () -> Unit,
33+
onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit
34+
) {
35+
detectDragGesturesAfterLongPress(onDragStart, onDragEnd, onDragCancel, onDrag)
36+
}
37+
}
38+
}

reorderable/src/commonMain/kotlin/sh/calvin/reorderable/ReorderableLazyCollection.kt

Lines changed: 12 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -657,12 +657,14 @@ interface ReorderableCollectionItemScope {
657657
* @param interactionSource [MutableInteractionSource] that will be used to emit [DragInteraction.Start] when this draggable is being dragged.
658658
* @param onDragStarted The function that is called when the item starts being dragged
659659
* @param onDragStopped The function that is called when the item stops being dragged
660+
* @param dragGestureDetector [DragGestureDetector] that will be used to detect drag gestures
660661
*/
661662
fun Modifier.draggableHandle(
662663
enabled: Boolean = true,
663664
interactionSource: MutableInteractionSource? = null,
664665
onDragStarted: (startedPosition: Offset) -> Unit = {},
665666
onDragStopped: () -> Unit = {},
667+
dragGestureDetector: DragGestureDetector = DragGestureDetector.Press
666668
): Modifier
667669

668670
/**
@@ -701,7 +703,8 @@ internal class ReorderableCollectionItemScopeImpl(
701703
interactionSource: MutableInteractionSource?,
702704
onDragStarted: (startedPosition: Offset) -> Unit,
703705
onDragStopped: () -> Unit,
704-
) = composed {
706+
dragGestureDetector: DragGestureDetector
707+
): Modifier = composed {
705708
var handleOffset by remember { mutableStateOf(Offset.Zero) }
706709
var handleSize by remember { mutableStateOf(IntSize.Zero) }
707710

@@ -714,6 +717,7 @@ internal class ReorderableCollectionItemScopeImpl(
714717
key1 = reorderableLazyCollectionState,
715718
enabled = enabled && (reorderableLazyCollectionState.isItemDragging(key).value || !reorderableLazyCollectionState.isAnyItemDragging),
716719
interactionSource = interactionSource,
720+
dragGestureDetector = dragGestureDetector,
717721
onDragStarted = {
718722
coroutineScope.launch {
719723
val handleOffsetRelativeToItem = handleOffset - itemPositionProvider()
@@ -733,7 +737,7 @@ internal class ReorderableCollectionItemScopeImpl(
733737
onDrag = { change, dragAmount ->
734738
change.consume()
735739
reorderableLazyCollectionState.onDrag(dragAmount)
736-
},
740+
}
737741
)
738742
}
739743

@@ -750,41 +754,14 @@ internal class ReorderableCollectionItemScopeImpl(
750754
interactionSource: MutableInteractionSource?,
751755
onDragStarted: (startedPosition: Offset) -> Unit,
752756
onDragStopped: () -> Unit,
753-
) = composed {
754-
var handleOffset by remember { mutableStateOf(Offset.Zero) }
755-
var handleSize by remember { mutableStateOf(IntSize.Zero) }
756-
757-
val coroutineScope = rememberCoroutineScope()
758-
759-
onGloballyPositioned {
760-
handleOffset = it.positionInRoot()
761-
handleSize = it.size
762-
}.longPressDraggable(
763-
key1 = reorderableLazyCollectionState,
764-
enabled = enabled && (reorderableLazyCollectionState.isItemDragging(key).value || !reorderableLazyCollectionState.isAnyItemDragging),
757+
) =
758+
draggableHandle(
759+
enabled = enabled,
765760
interactionSource = interactionSource,
766-
onDragStarted = {
767-
coroutineScope.launch {
768-
val handleOffsetRelativeToItem = handleOffset - itemPositionProvider()
769-
val handleCenter = Offset(
770-
handleOffsetRelativeToItem.x + handleSize.width / 2f,
771-
handleOffsetRelativeToItem.y + handleSize.height / 2f
772-
)
773-
774-
reorderableLazyCollectionState.onDragStart(key, handleCenter)
775-
}
776-
onDragStarted(it)
777-
},
778-
onDragStopped = {
779-
reorderableLazyCollectionState.onDragStop()
780-
onDragStopped()
781-
},
782-
onDrag = { change, dragAmount ->
783-
change.consume()
784-
reorderableLazyCollectionState.onDrag(dragAmount)
785-
},
761+
onDragStarted = onDragStarted,
762+
onDragStopped = onDragStopped,
763+
dragGestureDetector = DragGestureDetector.LongPress
786764
)
787-
}
788765
}
789766

790767
/**

reorderable/src/commonMain/kotlin/sh/calvin/reorderable/ReorderableList.kt

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import androidx.compose.animation.core.Spring
2121
import androidx.compose.animation.core.spring
2222
import androidx.compose.foundation.gestures.DraggableState
2323
import androidx.compose.foundation.gestures.Orientation
24-
import androidx.compose.foundation.gestures.draggable
2524
import androidx.compose.foundation.interaction.DragInteraction
2625
import androidx.compose.foundation.interaction.MutableInteractionSource
2726
import androidx.compose.foundation.layout.Arrangement
@@ -209,12 +208,14 @@ interface ReorderableScope {
209208
* @param interactionSource [MutableInteractionSource] that will be used to emit [DragInteraction.Start] when this draggable is being dragged
210209
* @param onDragStarted The function that is called when the item starts being dragged
211210
* @param onDragStopped The function that is called when the item stops being dragged
211+
* @param dragGestureDetector [DragGestureDetector] that will be used to detect drag gestures
212212
*/
213213
fun Modifier.draggableHandle(
214214
enabled: Boolean = true,
215-
onDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit = {},
216-
onDragStopped: suspend CoroutineScope.(velocity: Float) -> Unit = {},
215+
onDragStarted: (startedPosition: Offset) -> Unit = {},
216+
onDragStopped: (velocity: Float) -> Unit = {},
217217
interactionSource: MutableInteractionSource? = null,
218+
dragGestureDetector: DragGestureDetector = DragGestureDetector.Press
218219
): Modifier
219220

220221
/**
@@ -240,38 +241,20 @@ internal class ReorderableScopeImpl(
240241
) : ReorderableScope {
241242

242243
override fun Modifier.draggableHandle(
243-
enabled: Boolean,
244-
onDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit,
245-
onDragStopped: suspend CoroutineScope.(velocity: Float) -> Unit,
246-
interactionSource: MutableInteractionSource?,
247-
) = draggable(
248-
state = state.draggableStates[index],
249-
orientation = orientation,
250-
enabled = enabled && (state.isItemDragging(index).value || !state.isAnyItemDragging),
251-
interactionSource = interactionSource,
252-
onDragStarted = {
253-
state.startDrag(index)
254-
onDragStarted(it)
255-
},
256-
onDragStopped = { velocity ->
257-
launch { state.settle(index, velocity) }
258-
onDragStopped(velocity)
259-
},
260-
)
261-
262-
override fun Modifier.longPressDraggableHandle(
263244
enabled: Boolean,
264245
onDragStarted: (startedPosition: Offset) -> Unit,
265246
onDragStopped: (velocity: Float) -> Unit,
266247
interactionSource: MutableInteractionSource?,
248+
dragGestureDetector: DragGestureDetector,
267249
) = composed {
268250
val velocityTracker = remember { VelocityTracker() }
269251
val coroutineScope = rememberCoroutineScope()
270252

271-
longPressDraggable(
253+
draggable(
272254
key1 = state,
273255
enabled = enabled && (state.isItemDragging(index).value || !state.isAnyItemDragging),
274256
interactionSource = interactionSource,
257+
dragGestureDetector = dragGestureDetector,
275258
onDragStarted = {
276259
state.startDrag(index)
277260
onDragStarted(it)
@@ -299,6 +282,20 @@ internal class ReorderableScopeImpl(
299282
},
300283
)
301284
}
285+
286+
override fun Modifier.longPressDraggableHandle(
287+
enabled: Boolean,
288+
onDragStarted: (startedPosition: Offset) -> Unit,
289+
onDragStopped: (velocity: Float) -> Unit,
290+
interactionSource: MutableInteractionSource?,
291+
) =
292+
draggableHandle(
293+
enabled = enabled,
294+
onDragStarted = onDragStarted,
295+
onDragStopped = onDragStopped,
296+
interactionSource = interactionSource,
297+
dragGestureDetector = DragGestureDetector.LongPress
298+
)
302299
}
303300

304301
/**

reorderable/src/commonMain/kotlin/sh/calvin/reorderable/draggable.kt

Lines changed: 5 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package sh.calvin.reorderable
22

3-
import androidx.compose.foundation.gestures.detectDragGestures
4-
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
53
import androidx.compose.foundation.interaction.DragInteraction
64
import androidx.compose.foundation.interaction.MutableInteractionSource
75
import androidx.compose.runtime.DisposableEffect
@@ -21,6 +19,7 @@ internal fun Modifier.draggable(
2119
key1: Any?,
2220
enabled: Boolean = true,
2321
interactionSource: MutableInteractionSource? = null,
22+
dragGestureDetector: DragGestureDetector = DragGestureDetector.Press,
2423
onDragStarted: (Offset) -> Unit = { },
2524
onDragStopped: () -> Unit = { },
2625
onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit,
@@ -48,83 +47,12 @@ internal fun Modifier.draggable(
4847
}
4948

5049
pointerInput(key1, enabled) {
51-
if (enabled) {
52-
detectDragGestures(
53-
onDragStart = {
54-
dragStarted = true
55-
dragInteractionStart = DragInteraction.Start().also {
56-
coroutineScope.launch {
57-
interactionSource?.emit(it)
58-
}
59-
}
60-
61-
onDragStarted(it)
62-
},
63-
onDragEnd = {
64-
dragInteractionStart?.also {
65-
coroutineScope.launch {
66-
interactionSource?.emit(DragInteraction.Stop(it))
67-
}
68-
}
69-
70-
if (dragStarted) {
71-
onDragStopped()
72-
}
73-
74-
dragStarted = false
75-
},
76-
onDragCancel = {
77-
dragInteractionStart?.also {
78-
coroutineScope.launch {
79-
interactionSource?.emit(DragInteraction.Cancel(it))
80-
}
81-
}
82-
83-
if (dragStarted) {
84-
onDragStopped()
85-
}
86-
87-
dragStarted = false
88-
},
89-
onDrag = onDrag,
90-
)
91-
}
92-
}
93-
}
94-
95-
internal fun Modifier.longPressDraggable(
96-
key1: Any?,
97-
enabled: Boolean = true,
98-
interactionSource: MutableInteractionSource? = null,
99-
onDragStarted: (Offset) -> Unit = { },
100-
onDragStopped: () -> Unit = { },
101-
onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit,
102-
) = composed {
103-
val coroutineScope = rememberCoroutineScope()
104-
var dragInteractionStart by remember { mutableStateOf<DragInteraction.Start?>(null) }
105-
var dragStarted by remember { mutableStateOf(false) }
106-
107-
DisposableEffect(key1) {
108-
onDispose {
109-
if (dragStarted) {
110-
dragInteractionStart?.also {
111-
coroutineScope.launch {
112-
interactionSource?.emit(DragInteraction.Cancel(it))
113-
}
114-
}
115-
116-
if (dragStarted) {
117-
onDragStopped()
118-
}
119-
120-
dragStarted = false
121-
}
50+
if (!enabled) {
51+
return@pointerInput
12252
}
123-
}
12453

125-
pointerInput(key1, enabled) {
126-
if (enabled) {
127-
detectDragGesturesAfterLongPress(
54+
with(dragGestureDetector) {
55+
detect(
12856
onDragStart = {
12957
dragStarted = true
13058
dragInteractionStart = DragInteraction.Start().also {
@@ -147,7 +75,6 @@ internal fun Modifier.longPressDraggable(
14775
}
14876

14977
dragStarted = false
150-
15178
},
15279
onDragCancel = {
15380
dragInteractionStart?.also {
@@ -161,7 +88,6 @@ internal fun Modifier.longPressDraggable(
16188
}
16289

16390
dragStarted = false
164-
16591
},
16692
onDrag = onDrag,
16793
)

0 commit comments

Comments
 (0)