Skip to content

Commit 83b4765

Browse files
Update awaitPointerEvent modifiers
1 parent ca26dd7 commit 83b4765

File tree

4 files changed

+168
-117
lines changed

4 files changed

+168
-117
lines changed
Lines changed: 126 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,59 @@
11
package com.smarttoolfactory.tutorial1_1basics.chapter5_gesture
22

3+
import androidx.compose.foundation.Canvas
34
import androidx.compose.foundation.gestures.awaitFirstDown
45
import androidx.compose.foundation.gestures.forEachGesture
6+
import androidx.compose.runtime.rememberCoroutineScope
57
import androidx.compose.ui.Modifier
8+
import androidx.compose.ui.composed
69
import androidx.compose.ui.input.pointer.AwaitPointerEventScope
710
import androidx.compose.ui.input.pointer.PointerEvent
811
import androidx.compose.ui.input.pointer.PointerInputChange
912
import androidx.compose.ui.input.pointer.pointerInput
13+
import kotlinx.coroutines.CoroutineScope
14+
import kotlinx.coroutines.delay
15+
import kotlinx.coroutines.launch
1016

1117
enum class MotionEvent {
1218
Idle, Down, Move, Up
1319
}
1420

21+
22+
/**
23+
* Reads [awaitFirstDown], and [awaitPointerEvent] to
24+
* get [PointerInputChange] and motion event states
25+
* [onDown], [onMove], and [onUp].
26+
*
27+
* @param onDown is invoked when first pointer is down initially.
28+
* @param onMove one or multiple pointers are being moved on screen.
29+
* @param onUp last pointer is up
30+
* @param scope [CoroutineScope] is required to have a delay after first down
31+
* to give enough time for [onDown] to be processed before [onMove]
32+
*/
1533
suspend fun AwaitPointerEventScope.awaitPointerMotionEvent(
16-
onTouchEvent: (MotionEvent, PointerInputChange) -> Unit
34+
onDown: (PointerInputChange) -> Unit = {},
35+
onMove: (PointerInputChange) -> Unit = {},
36+
onUp: (PointerInputChange) -> Unit = {},
37+
scope: CoroutineScope
1738
) {
1839

1940
// Wait for at least one pointer to press down, and set first contact position
2041
val down: PointerInputChange = awaitFirstDown()
21-
onTouchEvent(MotionEvent.Down, down)
42+
onDown(down)
2243

2344
var pointer = down
2445
// Main pointer is the one that is down initially
2546
var pointerId = down.id
2647

48+
// If a move event is followed fast enough down is skipped, especially by Canvas
49+
// to prevent it we add 20ms delay after first touch
50+
var waitedAfterDown = false
51+
52+
scope.launch {
53+
delay(20)
54+
waitedAfterDown = true
55+
}
56+
2757
// This is preferred way in default Compose gesture codes
2858
// to loop gesture events and use consume or position changes to
2959
// break while loop
@@ -46,20 +76,38 @@ suspend fun AwaitPointerEventScope.awaitPointerMotionEvent(
4676
pointerId = pointerInputChange.id
4777
pointer = pointerInputChange
4878

49-
onTouchEvent(MotionEvent.Move, pointer)
79+
if (waitedAfterDown) {
80+
onMove(pointer)
81+
}
5082
} else {
5183
// All of the pointers are up
52-
onTouchEvent(MotionEvent.Up, pointer)
84+
onUp(pointer)
5385
break
5486
}
5587
}
5688
}
5789

90+
/**
91+
* Reads [awaitFirstDown], and [awaitPointerEvent] to
92+
* get [PointerInputChange] and motion event states
93+
* [onDown], [onMove], and [onUp]. Unlike overload of this function [onMove] returns
94+
* list of [PointerInputChange] to get data about all pointers that are on the screen.
95+
*
96+
* @param onDown is invoked when first pointer is down initially.
97+
* @param onMove one or multiple pointers are being moved on screen.
98+
* @param onUp last pointer is up
99+
*
100+
* ### Note
101+
* There is a 20ms delay after [onDown] to let composables process this event.
102+
* When pointer is moved fast [Canvas] is not able to react first down at each occurrence.
103+
*
104+
*/
105+
suspend fun AwaitPointerEventScope.awaitPointerMotionEvents(
58106

59-
suspend fun AwaitPointerEventScope.awaitPointerMotionEvent(
60107
onDown: (PointerInputChange) -> Unit = {},
61-
onMove: (PointerInputChange) -> Unit= {},
62-
onUp: (PointerInputChange) -> Unit= {},
108+
onMove: ( List<PointerInputChange>) -> Unit = {},
109+
onUp: (PointerInputChange) -> Unit = {},
110+
scope: CoroutineScope
63111
) {
64112

65113
// Wait for at least one pointer to press down, and set first contact position
@@ -70,6 +118,15 @@ suspend fun AwaitPointerEventScope.awaitPointerMotionEvent(
70118
// Main pointer is the one that is down initially
71119
var pointerId = down.id
72120

121+
// If a move event is followed fast enough down is skipped, especially by Canvas
122+
// to prevent it we add 20ms delay after first touch
123+
var waitedAfterDown = false
124+
125+
scope.launch {
126+
delay(20)
127+
waitedAfterDown = true
128+
}
129+
73130
// This is preferred way in default Compose gesture codes
74131
// to loop gesture events and use consume or position changes to
75132
// break while loop
@@ -92,81 +149,88 @@ suspend fun AwaitPointerEventScope.awaitPointerMotionEvent(
92149
pointerId = pointerInputChange.id
93150
pointer = pointerInputChange
94151

95-
onMove(pointer)
152+
if (waitedAfterDown) {
153+
onMove( event.changes)
154+
}
155+
96156
} else {
97157
// All of the pointers are up
98-
onUp(pointer)
158+
onUp( pointer)
99159
break
100160
}
101161
}
102162
}
103163

164+
165+
/**
166+
* Reads [awaitFirstDown], and [awaitPointerEvent] to
167+
* get [PointerInputChange] and motion event states
168+
* [onDown], [onMove], and [onUp].
169+
*
170+
* @param onDown is invoked when first pointer is down initially.
171+
* @param onMove one or multiple pointers are being moved on screen.
172+
* @param onUp last pointer is up
173+
*
174+
* ### Note
175+
* There is a 20ms delay after [onDown] to let composables process this event.
176+
* When pointer is moved fast [Canvas] is not able to react first down at each occurrance.
177+
*
178+
*/
104179
fun Modifier.awaitPointerMotionEvent(
105180
onDown: (PointerInputChange) -> Unit = {},
106-
onMove: (PointerInputChange) -> Unit= {},
107-
onUp: (PointerInputChange) -> Unit= {},
108-
) =
109-
this.then(
181+
onMove: (PointerInputChange) -> Unit = {},
182+
onUp: (PointerInputChange) -> Unit = {},
183+
) = composed(
184+
inspectorInfo = {
185+
name = "awaitPointerMotionEvent"
186+
},
187+
factory = {
188+
val scope = rememberCoroutineScope()
189+
110190
Modifier.pointerInput(Unit) {
111191
forEachGesture {
112192
awaitPointerEventScope {
113-
awaitPointerMotionEvent(onDown, onMove, onUp)
193+
awaitPointerMotionEvent(onDown, onMove, onUp, scope)
114194
}
115195
}
116196
}
117-
)
197+
}
198+
)
199+
200+
/**
201+
* Reads [awaitFirstDown], and [awaitPointerEvent] to
202+
* get [PointerInputChange] and motion event states
203+
* [onDown], [onMove], and [onUp].
204+
*
205+
* [onMove] returns list of [PointerEvent] that are on screen
206+
*
207+
* @param onDown is invoked when first pointer is down initially.
208+
* @param onMove one or multiple pointers are being moved on screen.
209+
* @param onUp last pointer is up
210+
*
211+
* ### Note
212+
* There is a 20ms delay after [onDown] to let composables process this event.
213+
* When pointer is moved fast [Canvas] is not able to react first down at each occurrance.
214+
*
215+
*/
216+
fun Modifier.awaitPointerMotionEvents(
217+
onDown: (PointerInputChange) -> Unit = {},
218+
onMove: (List<PointerInputChange>) -> Unit = {},
219+
onUp: (PointerInputChange) -> Unit = {},
220+
) = composed(
221+
inspectorInfo = {
222+
name = "awaitPointerMotionEvent"
223+
},
224+
factory = {
225+
val scope = rememberCoroutineScope()
118226

119-
fun Modifier.awaitPointerMotionEvent(onTouchEvent: (MotionEvent, PointerInputChange) -> Unit) =
120-
this.then(
121227
Modifier.pointerInput(Unit) {
122228
forEachGesture {
123229
awaitPointerEventScope {
124-
awaitPointerMotionEvent(onTouchEvent)
230+
awaitPointerMotionEvents(onDown, onMove, onUp, scope)
125231
}
126232
}
127233
}
128-
)
129-
130-
suspend fun AwaitPointerEventScope.awaitMotionEvent(
131-
onTouchEvent: (MotionEvent, List<PointerInputChange>) -> Unit
132-
) {
133-
134-
// Wait for at least one pointer to press down, and set first contact position
135-
val down: PointerInputChange = awaitFirstDown()
136-
onTouchEvent(MotionEvent.Down, listOf(down))
137-
138-
var pointer = down
139-
// Main pointer is the one that is down initially
140-
var pointerId = down.id
141-
142-
// This is preferred way in default Compose gesture codes
143-
// to loop gesture events and use consume or position changes to
144-
// break while loop
145-
while (true) {
146-
147-
val event: PointerEvent = awaitPointerEvent()
148-
149-
val anyPressed = event.changes.any { it.pressed }
150-
151-
// There are at least one pointer pressed
152-
if (anyPressed) {
153-
// Get pointer that is down, if first pointer is up
154-
// get another and use it if other pointers are also down
155-
// event.changes.first() doesn't return same order
156-
val pointerInputChange =
157-
event.changes.firstOrNull { it.id == pointerId }
158-
?: event.changes.first()
159-
160-
// Next time will check same pointer with this id
161-
pointerId = pointerInputChange.id
162-
pointer = pointerInputChange
163-
164-
onTouchEvent(MotionEvent.Move, event.changes)
165-
166-
} else {
167-
// All of the pointers are up
168-
onTouchEvent(MotionEvent.Up, listOf(pointer))
169-
break
170-
}
171234
}
172-
}
235+
)
236+

Tutorial1-1Basics/src/main/java/com/smarttoolfactory/tutorial1_1basics/chapter6_graphics/PathOption.kt

Lines changed: 0 additions & 7 deletions
This file was deleted.

Tutorial1-1Basics/src/main/java/com/smarttoolfactory/tutorial1_1basics/chapter6_graphics/Tutorial6_1_2CanvasBasics2.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,5 +409,5 @@ private val canvasModifier = Modifier
409409
.padding(8.dp)
410410
.shadow(1.dp)
411411
.background(Color.White)
412-
.fillMaxSize()
412+
.fillMaxWidth()
413413
.height(200.dp)

0 commit comments

Comments
 (0)