๊นจ์•Œ ๊ฐœ๋… ๐Ÿ“‘/Android

[Android] Pointer input - AwaitPointerEventScope()์˜ ๋ฉ”์†Œ๋“œ๋“ค

interfacer_han 2025. 2. 8. 16:50

#1 ๊ฐœ์š”

#1-1 ์ด์ „ ๊ฒŒ์‹œ๊ธ€

 

[Android] Pointer input - PointerInputChange, PointerEvent

#1 ๊ฐœ์š” ๋™์ž‘ ์ดํ•ดํ•˜๊ธฐ  |  Jetpack Compose  |  Android Developers์ด ํŽ˜์ด์ง€๋Š” Cloud Translation API๋ฅผ ํ†ตํ•ด ๋ฒˆ์—ญ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋™์ž‘ ์ดํ•ดํ•˜๊ธฐ ์ปฌ๋ ‰์…˜์„ ์‚ฌ์šฉํ•ด ์ •๋ฆฌํ•˜๊ธฐ ๋‚ด ํ™˜๊ฒฝ์„ค์ •์„ ๊ธฐ์ค€์œผ๋กœ ์ฝ˜ํ…์ธ ๋ฅผ ์ €

kenel.tistory.com

์œ„ ๊ฒŒ์‹œ๊ธ€์—์„œ AwaitPointerEventScope()์˜ ๊ฐ€์žฅ ๋Œ€ํ‘œ์ ์ธ ๋ฉ”์†Œ๋“œ์ธ awaitPointerEvent()์— ๋Œ€ํ•ด ๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ–ˆ๋‹ค. ๋ณธ ๊ฒŒ์‹œ๊ธ€์—์„  awaitPointerEvent()๋ฅผ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€ ๋ฉ”์†Œ๋“œ๋“ค์„ ์„ค๋ช…ํ•œ๋‹ค.
 

#1-2 ๊ณต์‹ ๋ฌธ์„œ

 

AwaitPointerEventScope  |  Android Developers

androidx.appsearch.builtintypes.properties

developer.android.com

์ด ์•„๋ž˜ ๋‚ด์šฉ๋ถ€ํ„ด, ์œ„์˜ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž‘์„ฑํ–ˆ๋‹ค. ์˜์–ด ๋Šฅ๋ ฅ์ž๋ผ๋ฉด ๋‚ด ๊ธ€๋ณด๋‹ค ์œ„์˜ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฝ๋Š” ํŽธ์ด ํ›จ์”ฌ ์ข‹์„ ๊ฒƒ์ด๋‹ค.
 

#2 ์šฉ์–ด ์ •๋ฆฌ

#2-1 ์ทจ์†Œ (Cancellation)

Pointer์˜ ๋™์ž‘์ด ๋๊นŒ์ง€ ์ˆ˜ํ–‰๋˜์ง€ ์•Š๊ณ  ์ค‘๊ฐ„์— ์‹คํŒจํ•จ์„ ์˜๋ฏธ. ์Šค๋งˆํŠธํฐ ํ™”๋ฉด ํšŒ์ „, ๊ฐ‘์ž๊ธฐ ํ™ˆ ํ™”๋ฉด์œผ๋กœ ๋‚˜๊ฐ€์ง ๋“ฑ์ด ์›์ธ. ๋ณธ ๊ฒŒ์‹œ๊ธ€์—์„  ํŠน๋ณ„ํžˆ ์ฃผํ™ฉ์ƒ‰ ๊ธ€์”จ๋กœ ํ‘œ์‹œํ•˜๊ฒ ๋‹ค.
 

#2-2 ํ„ฐ์น˜ ๋ฏผ๊ฐ๋„ (Touch Slop)

Pointer๋ฅผ ์–ผ๋งˆ์˜ ๊ฑฐ๋ฆฌ๋งŒํผ ์›€์ง์–ด์•ผ '๋“œ๋ž˜๊ทธ'๋กœ ์ธ์‹ํ•˜๋Š” ์ง€์˜ ๊ธฐ์ค€ ๊ฐ’ ๋˜๋Š” ๊ทธ๋ ‡๊ฒŒ ํ•ด์„œ ์ธ์‹๋œ ๋“œ๋ž˜๊ทธ. ๋ณธ ๊ฒŒ์‹œ๊ธ€์—์„  ํŠน๋ณ„ํžˆ ๋ณด๋ผ์ƒ‰ ๊ธ€์”จ๋กœ ํ‘œ๊ธฐํ•˜๊ฒ ๋‹ค.

 

#2-3 ์†Œ๋น„ (Consume)

 

[Android] Pointer input - PointerInputChange, PointerEvent

#1 ๊ฐœ์š” ๋™์ž‘ ์ดํ•ดํ•˜๊ธฐ | Jetpack Compose | Android Developers์ด ํŽ˜์ด์ง€๋Š” Cloud Translation API๋ฅผ ํ†ตํ•ด ๋ฒˆ์—ญ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋™์ž‘ ์ดํ•ดํ•˜๊ธฐ ์ปฌ๋ ‰์…˜์„ ์‚ฌ์šฉํ•ด ์ •๋ฆฌํ•˜๊ธฐ ๋‚ด ํ™˜๊ฒฝ์„ค์ •์„ ๊ธฐ์ค€์œผ๋กœ ์ฝ˜ํ…์ธ ๋ฅผ ์ €์žฅํ•˜๊ณ 

kenel.tistory.com

์œ„ ๊ฒŒ์‹œ๊ธ€ ์ฐธ์กฐ. ๋ณธ ๊ฒŒ์‹œ๊ธ€์—์„  ํŠน๋ณ„ํžˆ ํŒŒ๋ž€์ƒ‰ ๊ธ€์”จ๋กœ ํ‘œ๊ธฐํ•˜๊ฒ ๋‹ค.

 

#3 ๋ฉ”์†Œ๋“œ ๋ชฉ๋ก - ๊ธฐ๋ณธ

#3-1 awaitFirstDown()

suspend fun AwaitPointerEventScope.awaitFirstDown(
    requireUnconsumed: Boolean = true,
    pass: PointerEventPass = PointerEventPass.Main
): PointerInputChange

์„ค๋ช…
'์ฒซ๋ฒˆ์งธ' ํ„ฐ์น˜๋ฅผ ๊ธฐ๋‹ค๋ฆผ. '์ฒซ๋ฒˆ์งธ'๋ผ๋Š” ๋ง์€ ์–ด๋–ค ๋งฅ๋ฝ์œผ๋กœ ์‚ฌ์šฉ๋˜์—ˆ๋Š”๊ฐ€? ์˜ˆ๋ฅผ ๋“ค์–ด, ์Šค๋งˆํŠธํฐ ์‚ฌ์šฉ์ž๊ฐ€ ํ™”๋ฉด์— ์†๊ฐ€๋ฝ์„ ๋Œ€๊ณ  ์ด๋ฆฌ์ €๋ฆฌ ๋“œ๋ž˜๊ทธํ–ˆ๋‹ค๊ฐ€ ๋–ผ์—ˆ๋‹ค๊ณ  ์น˜์ž. ์ด ๊ฒฝ์šฐ '์ฒซ๋ฒˆ์งธ' ํ„ฐ์น˜๋Š” ํ™”๋ฉด์— ์†๊ฐ€๋ฝ์„ ๋Œ„ ์ˆœ๊ฐ„์˜ ํ„ฐ์น˜๋ฅผ ์˜๋ฏธํ•œ๋‹ค. ํ•จ์ˆ˜๋ช…์— ์žˆ๋Š” 'down'์ด ์ด ์˜๋ฏธ๋‹ค. ํ™”๋ฉด์— ์†๊ฐ€๋ฝ์„ ๋‚ด๋ ค(down)๋†“์€ ์ˆœ๊ฐ„์ด๋ผ๋Š” ๋Šฌ์•™์Šค๋‹ค. requireUnconsumed๋ฅผ false๋กœ ๋ช…์‹œํ•˜๋ฉด, ์ด๋ฏธ ์†Œ๋น„๋œ '์ฒซ๋ฒˆ์งธ' ํ„ฐ์น˜๋„ ๊ฐ์ง€ํ•œ๋‹ค.

๋ฐ˜ํ™˜

'์ฒซ๋ฒˆ์งธ' ํ„ฐ์น˜์— ํ•ด๋‹นํ•˜๋Š” PointerInputChange ๋ฐ˜ํ™˜.

 

#3-2 awaitLongPressOrCancellation()

suspend fun AwaitPointerEventScope.awaitLongPressOrCancellation(
    pointerId: PointerId
): PointerInputChange?

์„ค๋ช…
๊พน ๋ˆ„๋ฅด๋Š” ํด๋ฆญ ์ด๋ฒคํŠธ๋ฅผ ๊ธฐ๋‹ค๋ฆผ.

๋ฐ˜ํ™˜

๊พน ๋ˆ„๋ฅด๋Š” ํด๋ฆญ ์ด๋ฒคํŠธ์— ํ•ด๋‹นํ•˜๋Š” PointerInputChange๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ทจ์†Œ๋˜๋ฉด ๋Œ€์‹  null์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

#3-3 waitForUpOrCancellation()

suspend fun AwaitPointerEventScope.waitForUpOrCancellation(
    pass: PointerEventPass = PointerEventPass.Main
): PointerInputChange?

์„ค๋ช…
์†๊ฐ€๋ฝ์ด ํ™”๋ฉด์—์„œ ๋–ผ์–ด์งˆ(Up) ๊ฒƒ์„ ๊ธฐ๋‹ค๋ฆผ. awaitFirstDown()์˜ ๋ฐ˜๋Œ€๋‹ค.

๋ฐ˜ํ™˜

์ •์ƒ์ ์œผ๋กœ ์†๊ฐ€๋ฝ์ด ํ™”๋ฉด์—์„œ ๋–ผ์–ด์กŒ๋‹ค๋ฉด ํ•ด๋‹น๋˜๋Š” PointerInputChange๊ฐ€ ๋ฐ˜ํ™˜๋˜๊ณ , ์ทจ์†Œ๋˜๋ฉด ๋Œ€์‹  null์„ ๋ฐ˜ํ™˜.

 

#4 ๋ฉ”์†Œ๋“œ ๋ชฉ๋ก - ๋“œ๋ž˜๊ทธ ๊ด€๋ จ

#4-1 drag()

suspend fun AwaitPointerEventScope.drag(
    pointerId: PointerId,
    onDrag: (PointerInputChange) -> Unit
): Boolean

์„ค๋ช…
ํŠน์ • ํฌ์ธํ„ฐ์˜ ๋“œ๋ž˜๊ทธ๋ฅผ ์ถ”์ . ํŠน์ • ํฌ์ธํ„ฐ๊ฐ€ ๋“œ๋ž˜๊ทธ๋ฅผ ์ˆ˜ํ–‰ํ•  ๋•Œ๋งˆ๋‹ค ํ”„๋กœ๊ทธ๋ž˜๋จธ๊ฐ€ ๊ตฌํ˜„ํ•ด ๋„ฃ์€ onDrag() ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋จ. ํ„ฐ์น˜ ๋ฏผ๊ฐ๋„๋ฅผ ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š์œผ๋ฏ€๋กœ, ์•„์ฃผ ์‚ด์ง๋งŒ ์›€์ง์—ฌ๋„ ๊ฐ์ง€ํ•จ.

 

๋ฐ˜ํ™˜
null์„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ๋ฐ˜๋“œ์‹œ PointerInputChange ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜.

 

์šฉ๋ก€

modifier.pointerInput(Unit) {
    awaitPointerEventScope {
        val down = awaitFirstDown() // '์ฒซ๋ฒˆ์งธ' ํ„ฐ์น˜ ๊ฐ์ง€
        drag(down.id) { change -> 
            println("Dragging: ${change.position}") // ํ˜„์žฌ ํ„ฐ์น˜ ์ขŒํ‘œ ์ถœ๋ ฅ
        }
    }
}

 

#4-2 verticalDrag()

suspend fun AwaitPointerEventScope.verticalDrag(
    pointerId: PointerId,
    onDrag: (PointerInputChange) -> Unit
): Boolean

์„ค๋ช…
์ˆ˜์ง ๋“œ๋ž˜๊ทธ๋งŒ์„ ๊ฐ์ง€ํ•œ๋‹ค๋Š” ์ ์„ ์ œ์™ธํ•˜๋ฉด drag()์™€ ๋™์ผํ•˜๋‹ค.

 

#4-3 horizontalDrag()

suspend fun AwaitPointerEventScope.horizontalDrag(
    pointerId: PointerId,
    onDrag: (PointerInputChange) -> Unit
): Boolean

์„ค๋ช…
์ˆ˜ํ‰ ๋“œ๋ž˜๊ทธ๋งŒ์„ ๊ฐ์ง€ํ•œ๋‹ค๋Š” ์ ์„ ์ œ์™ธํ•˜๋ฉด drag()์™€ ๋™์ผํ•˜๋‹ค.

 

#4-4 awaitDragOrCancellation()

suspend fun AwaitPointerEventScope.awaitDragOrCancellation(pointerId: PointerId): PointerInputChange?

์„ค๋ช…
ํŠน์ • ํฌ์ธํ„ฐ์˜ '๋“œ๋ž˜๊ทธ๊ฐ€ ์‹œ์ž‘๋จ'๋ฅผ ๊ฐ์ง€. drag()๊ฐ€ ๋“œ๋ž˜๊ทธ๊ฐ€ ์ง„ํ–‰๋˜๋Š” ์ค‘์ธ ํฌ์ธํ„ฐ๋กœ๋ถ€ํ„ฐ ์‹ค์‹œ๊ฐ„ ์œ„์น˜๋ฅผ ์ถ”์ ํ•ด ์ „๋‹ฌ๋ฐ›๋Š” ์šฉ๋„๋ผ๋ฉด, ๋ณธ ํ•จ์ˆ˜์ธ awaitDragOrCancellation()์€ ๊ทธ์ € ๋“œ๋ž˜๊ทธ๊ฐ€ ์‹œ์ž‘๋˜์—ˆ์Œ์„ ๊ฐ์ง€ํ•จ. ๋”ฐ๋ผ์„œ drag()์ฒ˜๋Ÿผ onDrag() ํ•จ์ˆ˜ ๋”ฐ์œ„๋ฅผ ๋ณด์œ ํ•˜์ง€ ์•Š์Œ. ํ„ฐ์น˜ ๋ฏผ๊ฐ๋„๋ฅผ ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š์œผ๋ฏ€๋กœ, ์•„์ฃผ ์‚ด์ง๋งŒ ์›€์ง์—ฌ๋„ ๊ฐ์ง€ํ•จ.

๋ฐ˜ํ™˜

๋“œ๋ž˜๊ทธ๊ฐ€ ์‹œ์ž‘๋˜๋ฉด ํ•ด๋‹น ํฌ์ธํ„ฐ์˜ PointerInputChange ๋ฐ˜ํ™˜. ์ทจ์†Œ๋˜๋ฉด ๋Œ€์‹  null ๋ฐ˜ํ™˜.

 

์šฉ๋ก€

modifier.pointerInput(Unit) {
    awaitPointerEventScope {
        val down = awaitFirstDown() // ์ฒซ ๋ฒˆ์งธ ํ„ฐ์น˜ ๊ฐ์ง€
        val drag = awaitDragOrCancellation(down.id) // ๋“œ๋ž˜๊ทธ ์‹œ์ž‘ ๊ฐ์ง€ (์ทจ์†Œ๋  ์ˆ˜๋„ ์žˆ์Œ)

        if (drag != null) {
            println("๋“œ๋ž˜๊ทธ ์‹œ์ž‘๋จ: ${drag.position}")
        } else {
            println("๋“œ๋ž˜๊ทธ๊ฐ€ ์ทจ์†Œ๋จ") // ํ„ฐ์น˜๊ฐ€ ์ค‘๊ฐ„์— ์ทจ์†Œ๋˜์—ˆ์„ ๊ฒฝ์šฐ
        }
    }
}

 

#4-5 awaitVerticalDragOrCancellation()

suspend fun AwaitPointerEventScope.awaitVerticalDragOrCancellation(
    pointerId: PointerId
): PointerInputChange?

์„ค๋ช…
์ˆ˜์ง ๋“œ๋ž˜๊ทธ๋งŒ์„ ๊ฐ์ง€ํ•œ๋‹ค๋Š” ์ ์„ ์ œ์™ธํ•˜๋ฉด awaitDragOrCancellation()์™€ ๋™์ผํ•˜๋‹ค.

 

#4-6 awaitHorizontalDragOrCancellation()

suspend fun AwaitPointerEventScope.awaitHorizontalDragOrCancellation(
    pointerId: PointerId
): PointerInputChange?

์„ค๋ช…
์ˆ˜ํ‰ ๋“œ๋ž˜๊ทธ๋งŒ์„ ๊ฐ์ง€ํ•œ๋‹ค๋Š” ์ ์„ ์ œ์™ธํ•˜๋ฉด awaitDragOrCancellation() ์™€ ๋™์ผํ•˜๋‹ค.

 

#5 ๋ฉ”์†Œ๋“œ ๋ชฉ๋ก - ํ„ฐ์น˜ ๋ฏผ๊ฐ๋„ ๊ด€๋ จ

#5-1 awaitTouchSlopOrCancellation()

suspend fun AwaitPointerEventScope.awaitTouchSlopOrCancellation(
    pointerId: PointerId,
    onTouchSlopReached: (change: PointerInputChange, overSlop: Offset) -> Unit
): PointerInputChange?

์„ค๋ช…
ํ”„๋กœ๊ทธ๋ž˜๋จธ๊ฐ€ ๋ช…์‹œํ•œ ํ„ฐ์น˜ ๋ฏผ๊ฐ๋„๋ฅผ ์ดˆ๊ณผํ•ด์•ผ '๋“œ๋ž˜๊ทธ'๋ผ๊ณ  ์ธ์‹ํ•œ๋‹ค๋Š” ์ ์„ ์ œ์™ธํ•˜๋ฉด, awaitDragOrCancellation()์™€ ๊ฑฐ์˜ ๋™์ผํ•˜๋‹ค. ๋˜, awaitDragOrCancellation()์—๋Š” ์—†๋Š” (์‚ฌ์šฉ์ž๊ฐ€ ํŠน์ • ํ„ฐ์น˜ ๋ฏผ๊ฐ๋„๋ฅผ ์ดˆ๊ณผํ–ˆ์„ ๋•Œ ํ˜ธ์ถœ๋ ) ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋„ ์กด์žฌํ•œ๋‹ค. 

 

#5-2 awaitVerticalTouchSlopOrCancellation()

suspend fun AwaitPointerEventScope.awaitVerticalTouchSlopOrCancellation(
    pointerId: PointerId,
    onTouchSlopReached: (change: PointerInputChange, overSlop: Float) -> Unit
): PointerInputChange?

์„ค๋ช…
์ˆ˜์ง ๋“œ๋ž˜๊ทธ๋งŒ์„ ๊ฐ์ง€ํ•œ๋‹ค๋Š” ์ ์„ ์ œ์™ธํ•˜๋ฉด awaitTouchSlopOrCancellation()์™€ ๋™์ผํ•˜๋‹ค.

 

#5-3 awaitHorizontalTouchSlopOrCancellation()

suspend fun AwaitPointerEventScope.awaitHorizontalTouchSlopOrCancellation(
    pointerId: PointerId,
    onTouchSlopReached: (change: PointerInputChange, overSlop: Float) -> Unit
): PointerInputChange?

์„ค๋ช…
์ˆ˜ํ‰ ๋“œ๋ž˜๊ทธ๋งŒ์„ ๊ฐ์ง€ํ•œ๋‹ค๋Š” ์ ์„ ์ œ์™ธํ•˜๋ฉด awaitTouchSlopOrCancellation()์™€ ๋™์ผํ•˜๋‹ค.