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

[Android] Pointer input - PointerInputChange, PointerEvent

interfacer_han 2025. 2. 7. 19:14

#1 ๊ฐœ์š”

 

๋™์ž‘ ์ดํ•ดํ•˜๊ธฐ  |  Jetpack Compose  |  Android Developers

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

developer.android.com

์œ„ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋‚˜์˜ ์–ธ์–ด๋กœ ์ •๋ฆฌํ•˜๊ณ , ์ƒ˜ํ”Œ ์•ฑ์„ ๋งŒ๋“ค์–ด๋ดค๋‹ค.
 

#2 Pointer

#2-1 ํฌ์ธํ„ฐ๋Š” ํ•˜๋“œ์›จ์–ด๋‹ค

ํ™”๋ฉด์˜ ํŠน์ • ์ขŒํ‘œ๋ฅผ ์ฐ์„(point) ์ˆ˜ ์žˆ๋Š” ์‚ฌ๋ฌผ(ํ•˜๋“œ์›จ์–ด)์„ ์˜๋ฏธํ•œ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋ก  ์†๊ฐ€๋ฝ์„ ์˜๋ฏธํ•œ๋‹ค. ํ˜น์€ ๊ฐค๋Ÿญ์‹œ์˜ SํŽœ์ด ํ•ด๋‹น๋œ๋‹ค. ํ‚ค๋ณด๋“œ๋Š” ํ„ฐ์น˜ ์Šคํฌ๋ฆฐ์˜ ์–ด๋Š ์ขŒํ‘œ๋ฅผ ๊ฐ€๋ฆฌํ‚ฌ(point) ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ํฌ์ธํ„ฐ๊ฐ€ ์•„๋‹ˆ๋‹ค.
 

#2-2 PointerType

package androidx.compose.ui.input.pointer

...

/**
 * The device type that produces a [PointerInputChange], such as a mouse or stylus.
 */
@kotlin.jvm.JvmInline
value class PointerType private constructor(private val value: Int) {

    override fun toString(): String = when (value) {
        1 -> "Touch"
        2 -> "Mouse"
        3 -> "Stylus"
        4 -> "Eraser"
        else -> "Unknown"
    }

    companion object {
        /**
         * An unknown device type or the device type isn't relevant.
         */
        val Unknown = PointerType(0)

        /**
         * Touch (finger) input.
         */
        val Touch = PointerType(1)

        /**
         * A mouse pointer.
         */
        val Mouse = PointerType(2)

        /**
         * A stylus.
         */
        val Stylus = PointerType(3)

        /**
         * An eraser or an inverted stylus.
         */
        val Eraser = PointerType(4)
    }
}

...

(์ „์ฒด ์†Œ์Šค์ฝ”๋“œ)
 

์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ๋Š” ํฌ์ธํ„ฐ์˜ ์œ ํ˜•์ด ์œ„์˜ 5๊ฐ€์ง€๋กœ ๋ถ„๋ฅ˜๋œ๋‹ค. PointerType์€ ํ›„์ˆ ํ•  PointerInputChange์— ์ธ์ˆ˜๋กœ์„œ ์ „๋‹ฌ๋œ๋‹ค.
 

#3 PointerInputChange

#3-1 ์ฝ”๋“œ

package androidx.compose.ui.input.pointer

...

@Immutable
class PointerInputChange(
    val id: PointerId, // ์ธ์Šคํ„ด์Šค ์‹๋ณ„์„ ์œ„ํ•œ ID. ์ค‘๋ณต์ด ๊ฐ€๋Šฅํ•˜๋‹ค (์ƒ˜ํ”Œ ์•ฑ์—์„œ ํ™•์ธ ๊ฐ€๋Šฅ)
    val uptimeMillis: Long, // PointerInputChange ์ธ์Šคํ„ด์Šค๊ฐ€ ๋ฐœ์ƒํ•œ ์‹œ๊ฐ
    val position: Offset, // 'ํ™”๋ฉด ์ƒ์˜ ์ขŒํ‘œ'๊ฐ€ ์•„๋‹˜์— ์ฃผ์˜. ์„ค๋ช… ์ฐธ์กฐ
    val pressed: Boolean, // ํ„ฐ์น˜ ์Šคํฌ๋ฆฐ์„ ๋ˆ„๋ฅด๋Š” PointerInputChange์ธ์ง€, ๋–ผ๋Š” PointerInputChange์ธ์ง€ ๋ช…์‹œ
    val pressure: Float, // ํฌ์ธํ„ฐ๊ฐ€ ํ™”๋ฉด์„ ๋ˆ„๋ฅด๋Š” ์••๋ ฅ
    val previousUptimeMillis: Long, // ์ง์ „ PointerInputChange ์ธ์Šคํ„ด์Šค๊ฐ€ ๋ฐœ์ƒํ•œ ์‹œ๊ฐ
    val previousPosition: Offset, // ์ง์ „ PointerInputChange์˜ position
    val previousPressed: Boolean, // ์ง์ „ PointerInputChange์˜ pressed
    isInitiallyConsumed: Boolean, // ๋‹ค๋ฅธ ๊ณณ์—์„œ ์ด๋ฏธ ์จ๋จน์€ PointerInputChange์ž„์„ ๋ช…์‹œ
    val type: PointerType = PointerType.Touch, // #2-2 ์ฐธ์กฐ
    val scrollDelta: Offset = Offset.Zero // ๋งˆ์šฐ์Šค ํœ ์„ ๊ตด๋ฆฐ ์ •๋„๊ฐ’
) {
    ...
}

...

(์ „์ฒด ์†Œ์Šค์ฝ”๋“œ)
 

ํฌ์ธํ„ฐ(ํ•˜๋“œ์›จ์–ด)์˜ ์›€์ง์ž„์„ ํ„ฐ์น˜ ์Šคํฌ๋ฆฐ์œผ๋กœ ๊ธฐ๋กํ•˜์—ฌ, ๋””์ง€ํ„ธ๋ผ์ด์ง•(์†Œํ”„ํŠธ์›จ์–ด)ํ•œ ํด๋ž˜์Šค๋‹ค. ๋”ฐ๋ผ์„œ, ์•ˆ๋“œ๋กœ์ด๋“œ ๊ธฐ๊ธฐ์˜ ํ„ฐ์น˜ ์Šคํฌ๋ฆฐ์„ ์‚ฌ์šฉํ•  ๋•Œ๋งˆ๋‹ค ๋ฌด์ˆ˜ํžˆ ๋งŽ์€ PointerInputChange ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.
 

#3-2 position์— ๋Œ€ํ•œ ์ดํ•ด

์šฐ์„  ๋ชจ๋“  position(์œ„์น˜)์˜ ๊ธฐ์ค€์€ ๋งจ ์™ผ์ชฝ ๋งจ ์ƒ๋‹จ์ด๋‹ค. ์ฆ‰, ๋งจ โ†–์ชฝ์˜ ์ขŒํ‘œ๊ฐ€ (0, 0)์ด๋‹ค.
 
๋˜, ๊ธฐ์ค€์€ ์Šค๋งˆํŠธํฐ ํ™”๋ฉด์ด ์•„๋‹ˆ๋‹ค! ํ™”๋ฉด ์† ์ปดํฌ๋„ŒํŠธ๋“ค์ด ๊ฐ๊ฐ ๊ณ ์œ ํ•œ ๊ธฐ์ค€์„ ๊ฐ€์ง€๊ฒŒ ๋œ๋‹ค. ๋”ฐ๋ผ์„œ, ์œ„ ์Šค๋งˆํŠธํฐ ํ™”๋ฉด ๋„์‹๋„์—์„œ๋Š” ๊ธฐ์ค€์ด 4๊ฐœ ์กด์žฌํ•  ๊ฒƒ์ด๋‹ค (์ € 4๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ๋‹ด๋Š” ๋ ˆ์ด์•„์›ƒ ์ปดํฌ๋„ŒํŠธ์ธ Box()๊นŒ์ง€ ํฌํ•จํ•œ๋‹ค๋ฉด 5๊ฐœ).
 
์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž๊ฐ€ 1๋ฒˆ Text()์˜ B ๋ถ€๋ถ„์„ ํ„ฐ์น˜ํ•˜๋ฉด PointerInputChange.position์€ (2, 2)์ด ๋œ๋‹ค. 1๋ฒˆ Text()์˜ ๊ธฐ์ค€์ธ A์—์„œ ์˜ค๋ฅธ์ชฝ์œผ๋กœ 2๋งŒํผ ์•„๋ž˜์ชฝ์œผ๋กœ 2๋งŒํผ ๋–จ์–ด์ ธ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. D๋Š”? A์—์„œ ์˜ค๋ฅธ์ชฝ์œผ๋กœ -1๋งŒํผ ์•„๋ž˜์ชฝ์œผ๋กœ -1๋งŒํผ ๋–จ์–ด์ ธ ์žˆ์œผ๋ฏ€๋กœ (= ์™ผ์ชฝ์œผ๋กœ 1๋งŒํผ ์œ„์ชฝ์œผ๋กœ 1๋งŒํผ ๋–จ์–ด์ ธ ์žˆ์œผ๋ฏ€๋กœ) (-1, -1)์ด ๋œ๋‹ค.
 
๊ธฐ์ค€์€ ์ปดํฌ๋„ŒํŠธ์˜ ๊ฐฏ์ˆ˜๋งŒํผ ์กด์žฌํ•œ๋‹ค๊ณ  ํ–ˆ๋‹ค. C์˜ position์€, 1๋ฒˆ Text()๊ฐ€ ๊ธฐ์ค€์ด๋ผ๋ฉด (12, 7)์ด๊ณ  2๋ฒˆ Button()์ด ๊ธฐ์ค€์ด๋ผ๋ฉด (6, 0)์ด ๋œ๋‹ค.
 
๊ทธ๋Ÿฐ๋ฐ C๋‚˜ D๋Š” 1๋ฒˆ Text()์˜ ๋ฐ”๊นฅ ์˜์—ญ์ด๋‹ค. 1๋ฒˆ Text()์˜ ์˜์—ญ์ด ์•„๋‹Œ๋ฐ๋„ ์–ด์งธ์„œ PointerInputChange ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋  ์ˆ˜ ์žˆ๋Š”๊ฐ€? ๋ฐ”๋กœ, ์‚ฌ์šฉ์ž๊ฐ€ ์ปดํฌ๋„ŒํŠธ ๋ฐ–์—์„œ ํ„ฐ์น˜ํ•œ ํ›„ ์†๊ฐ€๋ฝ์„ ๋“œ๋ž˜๊ทธํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ ์•ˆ์œผ๋กœ ๋“ค์–ด์˜ค๊ฑฐ๋‚˜, ๋ฐ˜๋Œ€๋กœ ์ปดํฌ๋„ŒํŠธ ์•ˆ์—์„œ ํ„ฐ์น˜ํ•œ ํ›„ ์†๊ฐ€๋ฝ์„ ์ปดํฌ๋„ŒํŠธ ๋ฐ”๊นฅ ์˜์—ญ์œผ๋กœ์œผ๋กœ ๋“œ๋ž˜๊ทธํ•  ์ˆ˜๋„ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ๋ž˜์„œ PointerInputChange.position ๊ฐ’์ด ์Œ์ˆ˜(D)์ด๊ฑฐ๋‚˜ ์ปดํฌ๋„ŒํŠธ ํฌ๊ธฐ๋ณด๋‹ค ํฐ ๊ฐ’(C)์ด ๋  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.
 

#4 PointerEvent

#4-1 ์ฝ”๋“œ

package androidx.compose.ui.input.pointer

...

/**
 * Describes a pointer input change event that has occurred at a particular point in time.
 */
expect class PointerEvent internal constructor(
    changes: List<PointerInputChange>,
    internalPointerEvent: InternalPointerEvent?
) {
    /**
     * @param changes The changes.
     */
    constructor(changes: List<PointerInputChange>)

    /**
     * The changes.
     */
    val changes: List<PointerInputChange>

    /**
     * The state of buttons (e.g. mouse or stylus buttons) during this event.
     */
    val buttons: PointerButtons

    /**
     * The state of modifier keys during this event.
     */
    val keyboardModifiers: PointerKeyboardModifiers

    /**
     * The primary reason the [PointerEvent] was sent.
     */
    var type: PointerEventType
        internal set
}

...

(์ „์ฒด ์†Œ์Šค์ฝ”๋“œ)
 
PointerEvent๋Š” PointerInputChange์˜ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ณด์œ ํ•˜๋ฉฐ, ์กด์žฌ ๋ชฉ์ ์€ PointerInputChange์™€ ๋™์ผํ•˜๋‹ค. ์ฆ‰, ํฌ์ธํ„ฐ์˜ ์›€์ง์ž„์„ ๊ธฐ๋กํ•˜๊ธฐ ์œ„ํ•œ ํด๋ž˜์Šค๋‹ค. PointerInputChange์˜ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ณด์œ ํ•˜๋Š” ์ด์œ ๋Š” ๋ฉ€ํ‹ฐ ํ„ฐ์น˜์˜ ์กด์žฌ ๋•Œ๋ฌธ์ด๋‹ค. ์†๊ฐ€๋ฝ 1๊ฐœ๋กœ๋งŒ ํ„ฐ์น˜ํ•œ๋‹ค๋ฉด List<PointerInputChange>์—๋Š” ์›์†Œ๊ฐ€ ํ•˜๋‚˜๋งŒ ์กด์žฌํ•˜๊ฒŒ ๋˜์ง€๋งŒ, ์†๊ฐ€๋ฝ 2๊ฐœ ์ด์ƒ์„ ์‚ฌ์šฉํ•ด ๋™์‹œ์— ํ„ฐ์น˜ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ์›์†Œ๊ฐ€ 2๊ฐœ ์ด์ƒ ์กด์žฌํ•˜๊ฒŒ ๋œ๋‹ค.

#4-2 Modifier.pointerInput()

public fun Modifier.pointerInput(
    key1: Any?,
    block: suspend PointerInputScope.() -> Unit
): Modifier

pointerInput()์€ PointerInputScope๋ฅผ ๋ณด์œ ํ•œ๋‹ค. PointerInputScope ์˜์—ญ์—๋Š” ํ›„์ˆ ํ•  awaitPointerEventScope()๋‚˜ ๋‹ค์Œ ๊ฒŒ์‹œ๊ธ€์—์„œ ์„ค๋ช…ํ•  detectTapGesture() ๋“ฑ์„ ํ†ตํ•ด, PointerEvent๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•œ ๋งˆ๋””๋กœ, PointEvent ๊ด€๋ จ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์œ„ํ•œ ๊ธฐ์ดˆ ํ† ๋Œ€๋ผ ํ•  ์ˆ˜ ์žˆ๊ฒ ๋‹ค.
 
PointerInputScope()์— suspend ํ‚ค์›Œ๋“œ๊ฐ€ ๋ถ™์–ด ์žˆ๋Š” ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฆ‰, PointerInputScope() ์†์€ ๋น„๋™๊ธฐ ์ฝ”๋“œ์˜ ์˜์—ญ์ด ๋˜์–ด Coroutines ์ฝ”๋“œ๋“ค์„ ์œ„์น˜์‹œํ‚ฌ ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค๋Š” ์–˜๊ธฐ๋‹ค. ๋˜, ๊ทธ๋ž˜์•ผ๋งŒ(๋น„๋™๊ธฐ ์ฝ”๋“œ์˜ ์˜์—ญ์ด์–ด์•ผ๋งŒ) ํ•œ๋‹ค. PointerEvent ์ธ์Šคํ„ด์Šค๋Š” ๋ฌด์ˆ˜ํžˆ ๋งŽ์ด ์ƒ์„ฑ๋˜์–ด ์Ÿ์•„์ง€๋“ฏ์ด pointerInput()์—๊ฒŒ ์ „๋‹ฌ๋ ํ…๋ฐ, ์ด๋ฅผ ๋™๊ธฐ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋ ค๊ณ  ํ•˜๋ฉด ํ™”๋ฉด์ด ์—„์ฒญ๋‚˜๊ฒŒ ๋ฒ„๋ฒ…์ผ ๊ฒƒ์ด๋‹ค.
 
pointerInput()์— ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋Š” ์ธ์ˆ˜ ์ค‘์—” "key"๋ผ๋Š” ์ด๋ฆ„์˜ ์ธ์ˆ˜๊ฐ€ ์กด์žฌํ•œ๋‹ค. "key" ์ž๋ฆฌ์—๋Š” ์ฃผ๋กœ ๊ทธ ๊ฐ’์ด ๋ณ€ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด๊ฐ€ ๋“ค์–ด๊ฐ„๋‹ค. ๊ทธ๋ฆฌ๊ณ  Jetpack Compose์—์„œ๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌ๋œ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋ฉด Recomposition์ด ์ผ์–ด๋‚œ๋‹ค (๊ผญ State.value๊ฐ€ ๋ณ€ํ•ด์•ผ๋งŒ Recomposition์ด ์ผ์–ด๋‚˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋‹ค) . ๋‹ค์‹œ ๋งํ•ด ์ด๋Š” "key" ๊ฐ’์˜ ๋ณ€ํ™”์— ์˜ํ•œ Compose Recomposition ๋•Œ, pointerInput()์ด ์žฌ์‹คํ–‰๋œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค (๋ฌผ๋ก  ์•”์‹œ์ ์œผ๋กœ ์‹คํ–‰๋  ๊ฒƒ์ด๋ฏ€๋กœ ํ”„๋กœ๊ทธ๋ž˜๋จธ๊ฐ€ ํฌ๊ฒŒ ์‹ ๊ฒฝ์“ธ ๋ถ€๋ถ„์€ ์•„๋‹ ๊ฒƒ์ด๋‹ค).
 
pointerInput()์€ Modifier.pointerInput( ... ).pointerInput( ... ).pointerInput( ... )์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ ๋ฒˆ ๋ฉ”์†Œ๋“œ ์ฒด์ด๋‹ํ•  ์ˆ˜๋„ ์žˆ๋‹ค (#4-5 ์ฐธ์กฐ).
 

#4-3 AwaitPointerEventScope()

public abstract suspend fun <R> awaitPointerEventScope(
    block: suspend AwaitPointerEventScope.() -> R
): R

pointerInput() ๋‚ด๋ถ€์— ๋„ฃ์„ ์ˆ˜ ์žˆ๋Š” CoroutineScope()๋‹ค. PointerInputScope()๋Š” ์ด๋ฏธ ๋น„๋™๊ธฐ ์ฝ”๋“œ์˜ ์˜์—ญ์ธ๋ฐ ๊ทธ ๋‚ด๋ถ€์— ๋˜ ๊ตณ์ด ์ด CoroutineScope๋ฅผ ๋„ฃ๋Š” ์ด์œ ๋Š”, AwaitPointerEventScope()๊ฐ€ ์ผ๋ฐ˜์ ์ธ CoroutineScope()์™€๋Š” ๋‹ฌ๋ฆฌ ์—ฌ๋Ÿฌ PointEvent๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ์† ์‰ฝ๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์†Œ๋“œ๋“ค์„ ์ œ๊ณตํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ผ๋ฐ˜์ ์ธ CoroutineScope()๋งŒ์„ ํ™œ์šฉํ•˜๋ฉด ๊ต‰์žฅํžˆ ๋ณต์žกํ•œ ๋กœ์ง์„ ์งœ์•ผํ•  ํ…๋ฐ ๊ทธ๋Ÿฌํ•œ ์ˆ˜๊ณ ๋ฅผ ๋œ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.
 

#4-4 AwaitPointerEventScope()์˜ ๋ฉ”์†Œ๋“œ๋“ค

๊ฐ€์žฅ ๋Œ€ํ‘œ์ ์ธ ๋ฉ”์†Œ๋“œ: awaitPointerEvent()

suspend fun awaitPointerEvent(pass: PointerEventPass = PointerEventPass.Main): PointerEvent

PointerEvent๊ฐ€ ์ผ์–ด๋‚  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ(suspend)ํ–ˆ๋‹ค๊ฐ€, PointerEvent๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋ฐ˜ํ™˜ํ•œ๋‹ค.
 
๋‹ค๋ฅธ ๋ฉ”์†Œ๋“œ๋“ค

 

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

#1 ๊ฐœ์š”#1-1 ์ด์ „ ๊ฒŒ์‹œ๊ธ€ [Android] Pointer input - PointerInputChange, PointerEvent#1 ๊ฐœ์š” ๋™์ž‘ ์ดํ•ดํ•˜๊ธฐ  |  Jetpack Compose  |  Android Developers์ด ํŽ˜์ด์ง€๋Š” Cloud Translation API๋ฅผ ํ†ตํ•ด ๋ฒˆ์—ญ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋™์ž‘ ์ดํ•ดํ•˜

kenel.tistory.com

๋‚˜๋จธ์ง€ ๋ฉ”์†Œ๋“œ๋“ค์€ ์œ„ ๊ฒŒ์‹œ๊ธ€์—์„œ ์ด์–ด ์„ค๋ช…ํ•œ๋‹ค.

 

#4-5 ๋ฉ”์†Œ๋“œ ์ฒด์ด๋‹๊ณผ '์†Œ๋น„'

pointerInput()์€ Modifier.pointerInput( ... ).pointerInput( ... ).pointerInput( ... )์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ ๋ฒˆ ๋ฉ”์†Œ๋“œ ์ฒด์ด๋‹ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ์•„๋ž˜์˜ ์ฝ”๋“œ๋ฅผ ๋ณด์ž.
 

Modifier
    .pointerInput(Unit) {
        while (true) {
            val event = awaitPointerEventScope { awaitPointerEvent() }
            event.changes.forEach { change ->
                if(!change.isConsumed) {
                    println("์ฒซ๋ฒˆ์งธ pointerInput์—์„œ ์ฒ˜๋ฆฌ: ${change.id}")
                }
            }
        }
    }
    .pointerInput(Unit) {
        while (true) {
            val event = awaitPointerEventScope { awaitPointerEvent() }
            event.changes.forEach { change ->
                if(!change.isConsumed) {
                    println("๋‘๋ฒˆ์งธ pointerInput์—์„œ ์ฒ˜๋ฆฌ: ${change.id}")
                }
            }
        }
    }
    .pointerInput(Unit) {
        while (true) {
            val event = awaitPointerEventScope { awaitPointerEvent() }
            event.changes.forEach { change ->
                if(!change.isConsumed) {
                    println("์„ธ๋ฒˆ์งธ pointerInput์—์„œ ์ฒ˜๋ฆฌ: ${change.id}")
                }
            }
            
            event.changes.forEach { it.consume() } // ์ด๋ฒคํŠธ ์†Œ๋น„
        }
    }
    .pointerInput(Unit) {
        while (true) {
            val event = awaitPointerEventScope { awaitPointerEvent() }
            event.changes.forEach { change ->
                if(!change.isConsumed) {
                    println("๋„ค๋ฒˆ์งธ pointerInput์—์„œ ์ฒ˜๋ฆฌ: ${change.id}")
                }
            }
        }
    }

while(true) { ... }
์šฐ์„  ์ฝ”๋“œ ์†์— ์žˆ๋Š” while(true) { ... }์— ๋Œ€ํ•ด ์„ค๋ช…ํ•˜๊ณ  2๊ฐœ ์ด์ƒ์˜ ๋ฉ”์†Œ๋“œ ์ฒด์ด๋‹ ์„ค๋ช…์œผ๋กœ ๋„˜์–ด๊ฐ€๊ฒ ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ์šฐ๋ฆฌ๋Š” PointerEvent๋ฅผ ํ•œ ๋ฒˆ๋งŒ ๊ฐ์ง€ํ•˜๊ณ  ๋ง์ง€ ์•Š์„ ๊ฒƒ์ด๋‹ค. ๋”ฐ๋ผ์„œ awaitEventScope() ๋”ฐ์œ„๋ฅผ while(true) { ... }๋กœ ๊ฐ์‹ธ, ์ƒˆ๋กœ ์ƒ์„ฑ๋œ PointerEvent ์ธ์Šคํ„ด์Šค์— ๊ณ„์† ๋Œ€๊ธฐํ•˜๊ฒŒ๋” ๋งŒ๋“ค์–ด์ค€ ๊ฒƒ์ด๋‹ค.

2๊ฐœ ์ด์ƒ์˜ ๋ฉ”์†Œ๋“œ ์ฒด์ด๋‹, ์ด๋ฒคํŠธ ์ „ํŒŒ ๋ฐฉํ–ฅ
pointerInput()์ด ์ค‘๋ณต ๋ฉ”์†Œ๋“œ ์ฒด์ด๋‹๋˜๋ฉด, PointerEvent๋Š” pointerInput()์ด ๋ฉ”์†Œ๋“œ ์ฒด์ด๋‹๋œ ์ˆœ์„œ์˜ ์—ญ๋ฐฉํ–ฅ์œผ๋กœ ์ „๋‹ฌ๋œ๋‹ค. ์ฆ‰ println()์˜ ์ถœ๋ ฅ์€ "๋„ค๋ฒˆ์งธ pointerInput์—์„œ ... ์„ธ๋ฒˆ์งธ pointerInput์—์„œ ..."๊ฐ€ ๋ ํ…Œ๋‹ค. ํ—ˆ๋‚˜, ์™œ ๊ฐ‘์ž๊ธฐ ์—ญ๋ฐฉํ–ฅ์ธ๊ฐ€?

 

Modifier์˜ ๋ฉ”์†Œ๋“œ ์ฒด์ด๋‹์€ ๋” ๋จผ์ € ์ฒด์ด๋‹๋œ ์ชฝ์ผ์ˆ˜๋ก '๋ถ€๋ชจ'๋ผ ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด '๋ถ€๋ชจ'๋Š” UI ์„ค๊ณ„์ƒ์œผ๋กœ '์ž์‹'๋ณด๋‹ค ์šฐ์„ ํ•˜์ง€๋งŒ, ์ด๋ฒคํŠธ ์ „ํŒŒ์—์„œ๋งŒํผ์€ '์ž์‹'์„ ์šฐ์„ ์‹œํ‚จ๋‹ค. ์™œ๋ƒํ•˜๋ฉด Jetpack Compose๋Š” UI์— ๊ด€ํ•œ ๋Ÿฐํƒ€์ž„์ด๊ธฐ์—, ๋ถ€๋ชจ ์ชฝ์ผ์ˆ˜๋ก ์ปจํ…Œ์ด๋„ˆ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ปจํ…Œ์ด๋„ˆ(๋ถ€๋ชจ)๋Š” ๊ทธ ์† ์š”์†Œ๋“ค(์ž์‹)์˜ ์ด๋ฒคํŠธ์— ๋Œ€ํ•ด์„œ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๊ฐ€์งˆ ํ•„์š”๊ฐ€ ์—†๋‹ค. ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ๋จผ์ € ์ด๋ฒคํŠธ๋ฅผ (ํ›„์ˆ ํ• ) '์†Œ๋น„'ํ•ด๋ฒ„๋ฆฌ๋ฉด ๋‚ด๋ถ€ ์ž์‹ ์š”์†Œ๋“ค์€ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ๊ธฐํšŒ๋ฅผ ์žƒ์–ด๋ฒ„๋ฆฌ๋Š” ๊ฒƒ์ด๋‹ค. ๋”ฐ๋ผ์„œ ์ด๋ฒคํŠธ '์†Œ๋น„'์— ๊ด€ํ•ด์„œ๋Š”, ๊ทธ ์ „ํŒŒ ์ˆœ์„œ๊ฐ€ Modifier ๋ฉ”์†Œ๋“œ ์ฒด์ด๋‹์˜ ์—ญ๋ฐฉํ–ฅ์ด ๋˜๋„๋ก (์•ˆ๋“œ๋กœ์ด๋“œ OS ๊ฐœ๋ฐœํŒ€์˜ ์˜๋„์— ์˜ํ•ด) ๋งŒ๋“ค์–ด์ง„ ๊ฒƒ์ด๋‹ค.

 

'์†Œ๋น„ (consume)'
"๋‘๋ฒˆ์งธ pointerInput์—์„œ ... ์ฒซ๋ฒˆ์งธ pointerInput์—์„œ ..."๋Š” ์ถœ๋ ฅ๋˜์ง€ ์•Š์„ ๊ฒƒ์ด๋‹ค. ์™œ๋ƒํ•˜๋ฉด ์„ธ๋ฒˆ์งธ pointerInput()์—์„œ ์ด๋ฒคํŠธ๋ฅผ '์†Œ๋น„'ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. '์†Œ๋น„'๋Š” #3-1์— ์žˆ๋Š” PointerInputChange์˜ ํ”„๋กœํผํ‹ฐ ๋ชฉ๋ก์—์„œ ์ด๋ฏธ ํ•œ ๋ฒˆ ๋ดค์—ˆ๋‹ค. ์–ด๋–ค PointerInputChange์— ๋Œ€ํ•ด ๋‚ด๊ฐ€ ์˜๋„ํ•œ ๋™์ž‘์„ ์ „๋ถ€ ์ฒ˜๋ฆฌํ–ˆ๋‹ค๋ฉด, ๋” ์ด์ƒ ์ด PointerInputChange๋Š” ํ™œ์šฉ ๊ฐ€์น˜๊ฐ€ ์—†๋‹ค. ์•„๋‹ˆ, ํ™œ์šฉ ๊ฐ€์น˜๊ฐ€ ์—†๋Š” ๊ฑธ ๋„˜์–ด์„œ ์ž ์žฌ์  ์—๋Ÿฌ์˜ ์›์ธ์ด ๋œ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ PointerInputChange์— ํ”„๋กœ๊ทธ๋ž˜๋จธ๊ฐ€ '์†Œ๋น„ํ•จ'์ด๋ผ๋Š” ๋”ฑ์ง€๋ฅผ ๋ถ™์ด๊ณ , ์œ„ ์ฝ”๋“œ์—์„œ์ฒ˜๋Ÿผ if ๋ถ„๊ธฐ๋ฌธ์œผ๋กœ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค.

 

#5 ํ„ฐ์น˜ ๋กœ๊ทธ ํ™•์ธ ์•ฑ

#5-1 ๊ฐœ์š”

PointerEvent ๋ฐ ๊ทธ ์†์˜ PointerInputChange๋ฅผ Log ๋ฉ”์‹œ์ง€๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ์•ฑ์„ ๋งŒ๋“ค์—ˆ๋‹ค.
 

#5-2 ํ•ต์‹ฌ ์ฝ”๋“œ

Column(
    modifier = Modifier
        .fillMaxWidth()
        .padding(innerPadding)
        .verticalScroll(rememberScrollState()),
    verticalArrangement = Arrangement.Top,
    horizontalAlignment = Alignment.CenterHorizontally
) {
    repeat(20) {
        val componentName = "Item ${it + 1}"

        Text(
            text = componentName,
            modifier = Modifier
                .background(Color.LightGray)
                .pointerInput(Unit) { 
                    awaitPointerEventScope {
                        while (true) {
                            val event = awaitPointerEvent() // suspend ํ•จ์ˆ˜
                            Log.d(componentName, "PointerEvent.type = ${event.type}")
                            event.changes.forEach { change ->
                                Log.d(
                                    componentName,
                                    """
                                        PointerInputChange(
                                            id = ${change.id}, 
                                            uptimeMillis = ${change.uptimeMillis},
                                            position = ${change.position}, 
                                            pressed = ${change.pressed},
                                            pressure = ${change.pressure}, 
                                            previousUptimeMillis = ${change.previousUptimeMillis},
                                            previousPosition = ${change.previousPosition}, 
                                            previousPressed = ${change.previousPressed},
                                            isInitiallyConsumed = ${change.isConsumed}, 
                                            type = ${change.type},
                                            scrollDelta = ${change.scrollDelta} 
                                        ) 
                                    """.trimIndent()
                                )
                            }
                        }
                    }
                },
            fontSize = 48.sp,
        )
        Spacer(modifier = Modifier.height(48.dp))
    }
}

PointerInputChange ๋ฐ PointerEvent๋ฅผ Log๋ฅผ ํ†ตํ•ด ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ๋‹ค.

#5-3 ์™„์„ฑ๋œ ์•ฑ

 

android-practice/pointer-input/PointerInputChangeLogger at master · Kanmanemone/android-practice

Contribute to Kanmanemone/android-practice development by creating an account on GitHub.

github.com

#5-2์˜ ์ฝ”๋“œ๋ฅผ ์ ์šฉํ•œ ์•ฑ์ด๋‹ค.
 

#6 ์š”์•ฝ

์†๊ฐ€๋ฝ(ํ•˜๋“œ์›จ์–ด)์˜ ์›€์ง์ž„์„, PointerInputChange ๋˜๋Š” PointerEvent๋กœ ์ฝ์–ด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.
 

#7 ์ด์–ด์ง€๋Š” ๊ธ€

 

[Android] Pointer input - Gesture

#1 ๊ฐœ์š”#1-1 ์ด์ „ ๊ฒŒ์‹œ๊ธ€ [Android] Pointer input - PointerInputChange, PointerEvent#1 ๊ฐœ์š” ๋™์ž‘ ์ดํ•ดํ•˜๊ธฐ  |  Jetpack Compose  |  Android Developers์ด ํŽ˜์ด์ง€๋Š” Cloud Translation API๋ฅผ ํ†ตํ•ด ๋ฒˆ์—ญ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋™์ž‘ ์ดํ•ดํ•˜

kenel.tistory.com