हाथ के जेस्चर के बारे में जानकारी

किसी ऐप्लिकेशन में जेस्चर हैंडलिंग पर काम करते समय, कई शब्दों और कॉन्सेप्ट को समझना ज़रूरी है. इस पेज पर, पॉइंटर, पॉइंटर इवेंट, और जेस्चर के बारे में बताया गया है. साथ ही, जेस्चर के लिए अलग-अलग ऐब्स्ट्रैक्शन लेवल के बारे में जानकारी दी गई है. इसमें इवेंट के इस्तेमाल और उसे आगे बढ़ाने के बारे में भी ज़्यादा जानकारी दी गई है.

परिभाषाएं

इस पेज पर मौजूद अलग-अलग कॉन्सेप्ट को समझने के लिए, आपको इस्तेमाल की गई कुछ शब्दावली को समझना होगा:

  • पॉइंटर: यह एक फ़िज़िकल ऑब्जेक्ट होता है. इसका इस्तेमाल करके, ऐप्लिकेशन के साथ इंटरैक्ट किया जा सकता है. मोबाइल डिवाइसों के लिए, सबसे आम पॉइंटर आपकी उंगली होती है, जो टचस्क्रीन से इंटरैक्ट करती है. इसके अलावा, उंगली के बजाय स्टाइलस का इस्तेमाल किया जा सकता है. बड़ी स्क्रीन वाले डिवाइसों पर, डिसप्ले को इनडायरेक्ट तरीके से इस्तेमाल करने के लिए, माउस या ट्रैकपैड का इस्तेमाल किया जा सकता है. किसी इनपुट डिवाइस को पॉइंटर के तौर पर इस्तेमाल करने के लिए, उसे किसी कोऑर्डिनेट पर "पॉइंट" करने में सक्षम होना चाहिए. उदाहरण के लिए, कीबोर्ड को पॉइंटर के तौर पर इस्तेमाल नहीं किया जा सकता. Compose में, PointerType का इस्तेमाल करके, पॉइंटर में होने वाले बदलावों में पॉइंटर टाइप को शामिल किया जाता है.
  • पॉइंटर इवेंट: इससे किसी खास समय पर, ऐप्लिकेशन के साथ एक या उससे ज़्यादा पॉइंटर के लो-लेवल इंटरैक्शन के बारे में पता चलता है. पॉइंटर से होने वाला कोई भी इंटरैक्शन, जैसे कि स्क्रीन पर उंगली रखना या माउस को खींचना और छोड़ना, किसी इवेंट को ट्रिगर करेगा. Compose में, ऐसे इवेंट की सभी ज़रूरी जानकारी PointerEvent क्लास में मौजूद होती है.
  • जेस्चर: पॉइंटर इवेंट का एक क्रम, जिसे एक कार्रवाई के तौर पर समझा जा सकता है. उदाहरण के लिए, टैप जेस्चर को डाउन इवेंट के बाद अप इवेंट का क्रम माना जा सकता है. कई ऐप्लिकेशन में, टैप करने, खींचने और छोड़ने या ट्रांसफ़ॉर्म करने जैसे सामान्य जेस्चर इस्तेमाल किए जाते हैं. हालांकि, ज़रूरत पड़ने पर अपनी पसंद के मुताबिक जेस्चर भी बनाए जा सकते हैं.

अलग-अलग लेवल पर ऐब्स्ट्रैक्शन

Jetpack Compose, जेस्चर को मैनेज करने के लिए अलग-अलग लेवल के ऐब्स्ट्रैक्शन उपलब्ध कराता है. सबसे ऊपर, कॉम्पोनेंट सपोर्ट होता है. Button जैसे कंपोज़ेबल में, जेस्चर की सुविधा अपने-आप शामिल होती है. कस्टम कॉम्पोनेंट में जेस्चर की सुविधा जोड़ने के लिए, किसी भी कंपोज़ेबल में जेस्चर मॉडिफ़ायर जोड़े जा सकते हैं. जैसे, clickable. आखिर में, अगर आपको पसंद के मुताबिक जेस्चर चाहिए, तो pointerInput मॉडिफ़ायर का इस्तेमाल करें.

नियम के तौर पर, एब्स्ट्रैक्शन के सबसे ऊंचे लेवल पर बनाएं, जो आपको ज़रूरी फ़ंक्शन उपलब्ध कराता है. इस तरह, आपको लेयर में शामिल सबसे सही तरीकों का फ़ायदा मिलता है. उदाहरण के लिए, Button में clickable के मुकाबले, सुलभता के लिए इस्तेमाल की जाने वाली ज़्यादा सिमैंटिक जानकारी होती है. वहीं, clickable में रॉ pointerInput लागू करने के मुकाबले ज़्यादा जानकारी होती है.

कॉम्पोनेंट से जुड़ी सहायता

Compose में पहले से मौजूद कई कॉम्पोनेंट में, जेस्चर को हैंडल करने की सुविधा होती है. उदाहरण के लिए, LazyColumn अपने कॉन्टेंट को स्क्रोल करके, खींचने और छोड़ने के जेस्चर का जवाब देता है. वहीं, Button को दबाने पर, रिपल इफ़ेक्ट दिखता है. इसके अलावा, SwipeToDismiss कॉम्पोनेंट में, किसी एलिमेंट को खारिज करने के लिए स्वाइप करने का लॉजिक होता है. इस तरह के जेस्चर हैंडलिंग अपने-आप काम करते हैं.

जेस्चर को अंदरूनी तौर पर हैंडल करने के अलावा, कई कॉम्पोनेंट के लिए यह भी ज़रूरी है कि कॉलर, जेस्चर को हैंडल करे. उदाहरण के लिए, Button टैप का अपने-आप पता लगाता है और क्लिक इवेंट को ट्रिगर करता है. जेस्चर पर प्रतिक्रिया देने के लिए, Button को onClick लैम्ब्डा पास किया जाता है. इसी तरह, Slider में onValueChange लैम्डा जोड़ें, ताकि उपयोगकर्ता के स्लाइडर हैंडल को खींचने पर प्रतिक्रिया दी जा सके.

जब यह आपके इस्तेमाल के उदाहरण के हिसाब से सही हो, तो कॉम्पोनेंट में शामिल जेस्चर को प्राथमिकता दें. ऐसा इसलिए, क्योंकि इनमें फ़ोकस और सुलभता के लिए पहले से मौजूद सहायता शामिल होती है. साथ ही, इनकी अच्छी तरह से जांच की जाती है. उदाहरण के लिए, Button को खास तरीके से मार्क किया जाता है, ताकि सुलभता सेवाएं इसे क्लिक किए जा सकने वाले किसी एलिमेंट के बजाय, बटन के तौर पर सही तरीके से दिखाएं:

// Talkback: "Click me!, Button, double tap to activate"
Button(onClick = { /* TODO */ }) { Text("Click me!") }
// Talkback: "Click me!, double tap to activate"
Box(Modifier.clickable { /* TODO */ }) { Text("Click me!") }

Compose में सुलभता के बारे में ज़्यादा जानने के लिए, Compose में सुलभता लेख पढ़ें.

मॉडिफ़ायर की मदद से, किसी भी कंपोज़ेबल में जेस्चर जोड़ना

किसी भी कंपोज़ेबल पर जेस्चर मॉडिफ़ायर लागू किए जा सकते हैं, ताकि कंपोज़ेबल जेस्चर को सुन सके. उदाहरण के लिए, किसी सामान्य Box को clickable बनाकर, टैप जेस्चर को हैंडल करने की अनुमति दी जा सकती है. इसके अलावा, verticalScroll लागू करके, किसी Column को वर्टिकल स्क्रोल हैंडल करने की अनुमति दी जा सकती है.

अलग-अलग तरह के जेस्चर को हैंडल करने के लिए, कई मॉडिफ़ायर होते हैं:

आम तौर पर, कस्टम जेस्चर हैंडलिंग के बजाय, आउट-ऑफ़-द-बॉक्स जेस्चर मॉडिफ़ायर को प्राथमिकता दें. मॉडिफ़ायर, पॉइंटर इवेंट को हैंडल करने के अलावा, और भी सुविधाएं जोड़ते हैं. उदाहरण के लिए, clickable मॉडिफ़ायर, न सिर्फ़ प्रेस और टैप का पता लगाने की सुविधा जोड़ता है, बल्कि सिमैंटिक जानकारी, इंटरैक्शन के विज़ुअल इंडिकेशन, कर्सर घुमाने, फ़ोकस करने, और कीबोर्ड की सुविधा भी जोड़ता है. clickableका सोर्स कोड देखकर, यह पता लगाया जा सकता है कि इस सुविधा को कैसे जोड़ा जा रहा है.

pointerInput मॉडिफ़ायर की मदद से, किसी भी कंपोज़ेबल में कस्टम जेस्चर जोड़ना

हर जेस्चर को, पहले से मौजूद जेस्चर मॉडिफ़ायर की मदद से लागू नहीं किया जाता. उदाहरण के लिए, दबाकर रखने के बाद खींचने और छोड़ने, कंट्रोल-क्लिक करने या तीन उंगलियों से टैप करने पर, कार्रवाई बदलने वाली कुंजी का इस्तेमाल नहीं किया जा सकता. इसके बजाय, इन कस्टम जेस्चर की पहचान करने के लिए, अपना जेस्चर हैंडलर लिखा जा सकता है. pointerInput मॉडिफ़ायर की मदद से, जेस्चर हैंडलर बनाया जा सकता है. इससे आपको रॉ पॉइंटर इवेंट का ऐक्सेस मिलता है.

नीचे दिया गया कोड, रॉ पॉइंटर इवेंट को मॉनिटर करता है:

@Composable
private fun LogPointerEvents(filter: PointerEventType? = null) {
    var log by remember { mutableStateOf("") }
    Column {
        Text(log)
        Box(
            Modifier
                .size(100.dp)
                .background(Color.Red)
                .pointerInput(filter) {
                    awaitPointerEventScope {
                        while (true) {
                            val event = awaitPointerEvent()
                            // handle pointer event
                            if (filter == null || event.type == filter) {
                                log = "${event.type}, ${event.changes.first().position}"
                            }
                        }
                    }
                }
        )
    }
}

इस स्निपेट को अलग-अलग हिस्सों में बांटने पर, ये मुख्य कॉम्पोनेंट मिलते हैं:

  • pointerInput मॉडिफ़ायर. इसे एक या उससे ज़्यादा कुंजियां पास की जाती हैं. जब इनमें से किसी एक बटन की वैल्यू बदलती है, तो मॉडिफ़ायर कॉन्टेंट लैम्डा को फिर से लागू किया जाता है. सैंपल, कंपोज़ेबल में एक वैकल्पिक फ़िल्टर पास करता है. अगर उस फ़िल्टर की वैल्यू बदलती है, तो पॉइंटर इवेंट हैंडलर को फिर से लागू किया जाना चाहिए, ताकि यह पक्का किया जा सके कि सही इवेंट लॉग किए गए हैं.
  • awaitPointerEventScope एक कोरूटीन स्कोप बनाता है. इसका इस्तेमाल, पॉइंटर इवेंट के लिए इंतज़ार करने के लिए किया जा सकता है.
  • awaitPointerEvent, कोरूटीन को तब तक निलंबित रखता है, जब तक कि अगला पॉइंटर इवेंट नहीं होता.

रॉ इनपुट इवेंट को सुनना एक बेहतरीन तरीका है. हालांकि, इस रॉ डेटा के आधार पर कस्टम जेस्चर लिखना भी मुश्किल है. कस्टम जेस्चर बनाने की प्रोसेस को आसान बनाने के लिए, कई यूटिलिटी मेथड उपलब्ध हैं.

हाथ के सभी जेस्चर का पता लगाना

रॉ पॉइंटर इवेंट को हैंडल करने के बजाय, कुछ जेस्चर को सुना जा सकता है और सही तरीके से जवाब दें. AwaitPointerEventScope, इन इवेंट को सुनने के तरीके उपलब्ध कराता है:

ये टॉप-लेवल डिटेक्टर हैं. इसलिए, एक pointerInput मॉडिफ़ायर में कई डिटेक्टर नहीं जोड़े जा सकते. नीचे दिया गया स्निपेट सिर्फ़ टैप का पता लगाता है, खींचने और छोड़ने का नहीं:

var log by remember { mutableStateOf("") }
Column {
    Text(log)
    Box(
        Modifier
            .size(100.dp)
            .background(Color.Red)
            .pointerInput(Unit) {
                detectTapGestures { log = "Tap!" }
                // Never reached
                detectDragGestures { _, _ -> log = "Dragging" }
            }
    )
}

अंदरूनी तौर पर, detectTapGestures तरीका कोरूटीन को ब्लॉक करता है और दूसरा डिटेक्टर कभी नहीं पहुंचता. अगर आपको किसी कंपोज़ेबल में एक से ज़्यादा जेस्चर लिसनर जोड़ने हैं, तो अलग-अलग pointerInput मॉडिफ़ायर इंस्टेंस का इस्तेमाल करें:

var log by remember { mutableStateOf("") }
Column {
    Text(log)
    Box(
        Modifier
            .size(100.dp)
            .background(Color.Red)
            .pointerInput(Unit) {
                detectTapGestures { log = "Tap!" }
            }
            .pointerInput(Unit) {
                // These drag events will correctly be triggered
                detectDragGestures { _, _ -> log = "Dragging" }
            }
    )
}

हर जेस्चर के लिए इवेंट मैनेज करना

परिभाषा के मुताबिक, जेस्चर की शुरुआत पॉइंटर डाउन इवेंट से होती है. हर रॉ इवेंट से गुज़रने वाले while(true) लूप के बजाय, awaitEachGesture हेल्पर तरीके का इस्तेमाल किया जा सकता है. जब सभी पॉइंटर हटा दिए जाते हैं, तब awaitEachGesture मेथड, कंटेनर ब्लॉक को रीस्टार्ट करता है. इससे पता चलता है कि जेस्चर पूरा हो गया है:

@Composable
private fun SimpleClickable(onClick: () -> Unit) {
    Box(
        Modifier
            .size(100.dp)
            .pointerInput(onClick) {
                awaitEachGesture {
                    awaitFirstDown().also { it.consume() }
                    val up = waitForUpOrCancellation()
                    if (up != null) {
                        up.consume()
                        onClick()
                    }
                }
            }
    )
}

आम तौर पर, आपको हमेशा awaitEachGesture का इस्तेमाल करना चाहिए. हालांकि, अगर आपको जेस्चर की पहचान किए बिना पॉइंटर इवेंट का जवाब देना है, तो इसका इस्तेमाल न करें. इसका एक उदाहरण है hoverable, जो पॉइंटर डाउन या अप इवेंट का जवाब नहीं देता— इसे सिर्फ़ यह जानना होता है कि पॉइंटर कब अपनी सीमाओं में आता है या बाहर निकलता है.

किसी खास इवेंट या सब-जेस्चर का इंतज़ार करना

यहां कुछ ऐसे तरीके दिए गए हैं जिनसे जेस्चर के सामान्य हिस्सों की पहचान करने में मदद मिलती है:

  • awaitFirstDown का इस्तेमाल करके, पॉइंटर के नीचे जाने तक रोकें या सभी पॉइंटर के ऊपर जाने तक इंतज़ार करें. इसके लिए, waitForUpOrCancellation का इस्तेमाल करें.
  • awaitTouchSlopOrCancellation और awaitDragOrCancellation का इस्तेमाल करके, लो-लेवल ड्रैग लिसनर बनाएं. जेस्चर हैंडलर पहले तब तक निलंबित रहता है, जब तक पॉइंटर टच स्लॉप तक नहीं पहुंच जाता. इसके बाद, वह तब तक निलंबित रहता है, जब तक कि पहला ड्रैग इवेंट नहीं हो जाता. अगर आपको सिर्फ़ एक ऐक्सिस पर ड्रैग करना है, तो awaitHorizontalTouchSlopOrCancellation के साथ awaitHorizontalDragOrCancellation या awaitVerticalTouchSlopOrCancellation के साथ awaitVerticalDragOrCancellation का इस्तेमाल करें.
  • awaitLongPressOrCancellation को दबाकर रखने तक, इस सुविधा को रोकें.
  • ड्रैग इवेंट को लगातार सुनने के लिए, drag तरीके का इस्तेमाल करें. इसके अलावा, एक ऐक्सिस पर ड्रैग इवेंट सुनने के लिए, horizontalDrag या verticalDrag का इस्तेमाल करें.

मल्टी-टच इवेंट के लिए कैलकुलेशन लागू करना

जब कोई उपयोगकर्ता एक से ज़्यादा पॉइंटर का इस्तेमाल करके मल्टी-टच जेस्चर करता है, तो रॉ वैल्यू के आधार पर ज़रूरी ट्रांसफ़ॉर्मेशन को समझना मुश्किल होता है. अगर transformable मॉडिफ़ायर या detectTransformGestures तरीके आपके इस्तेमाल के उदाहरण के लिए, ज़रूरत के मुताबिक कंट्रोल नहीं दे रहे हैं, तो रॉ इवेंट को सुनें और उन पर कैलकुलेशन लागू करें. ये हेल्पर मेथड calculateCentroid, calculateCentroidSize, calculatePan, calculateRotation, और calculateZoom हैं.

इवेंट डिस्पैच करना और हिट-टेस्टिंग

हर पॉइंटर इवेंट, हर pointerInput मॉडिफ़ायर को नहीं भेजा जाता. इवेंट डिस्पैच करने की सुविधा इस तरह काम करती है:

  • पॉइंटर इवेंट, कंपोज़ेबल हैरारकी पर भेजे जाते हैं. जैसे ही कोई नया पॉइंटर अपना पहला पॉइंटर इवेंट ट्रिगर करता है, सिस्टम "ज़रूरी शर्तें पूरी करने वाले" कंपोज़ेबल की हिट-टेस्टिंग शुरू कर देता है. किसी कंपोज़ेबल को तब ज़रूरी शर्तें पूरी करने वाला माना जाता है, जब उसमें पॉइंटर इनपुट को मैनेज करने की सुविधाएं हों. हिट-टेस्टिंग, यूज़र इंटरफ़ेस (यूआई) ट्री के सबसे ऊपर से लेकर सबसे नीचे तक होती है. जब पॉइंटर इवेंट, कंपोज़ेबल की सीमाओं के अंदर होता है, तो कंपोज़ेबल को "हिट" किया जाता है. इस प्रोसेस से, कंपोज़ेबल की एक चेन बनती है जो हिट-टेस्ट में पॉज़िटिव नतीजे देती है.
  • डिफ़ॉल्ट रूप से, ट्री के एक ही लेवल पर ज़रूरी शर्तें पूरी करने वाले कई कंपोज़ेबल होने पर, सिर्फ़ सबसे ज़्यादा z-इंडेक्स वाले कंपोज़ेबल को "हिट" किया जाता है. उदाहरण के लिए, जब किसी Box में ओवरलैप होने वाले दो Button कॉम्पोज़ेबल जोड़े जाते हैं, तो सिर्फ़ सबसे ऊपर मौजूद कॉम्पोज़ेबल को पॉइंटर इवेंट मिलते हैं. सिद्धांत के तौर पर, इस व्यवहार को बदला जा सकता है. इसके लिए, आपको अपना PointerInputModifierNode लागू करना होगा और sharePointerInputWithSiblings को 'सही' पर सेट करना होगा.
  • एक ही पॉइंटर के लिए अन्य इवेंट, कंपोज़ेबल की उसी चेन पर भेजे जाते हैं और इवेंट प्रोपेगेशन लॉजिक के हिसाब से फ़्लो करते हैं. सिस्टम, इस पॉइंटर के लिए कोई और हिट-टेस्टिंग नहीं करता. इसका मतलब है कि चेन में मौजूद हर कंपोज़ेबल को उस पॉइंटर के लिए सभी इवेंट मिलते हैं. भले ही, वे इवेंट उस कंपोज़ेबल की सीमाओं से बाहर हों. चेन में शामिल नहीं किए गए कंपोज़ेबल को कभी भी पॉइंटर इवेंट नहीं मिलते. भले ही, पॉइंटर उनकी सीमाओं के अंदर हो.

माउस या स्टाइलस को होवर करने से ट्रिगर होने वाले होवर इवेंट, यहां बताए गए नियमों के अपवाद हैं. होवर इवेंट, उन सभी कंपोज़ेबल को भेजे जाते हैं जिन पर वे हिट करते हैं. इसलिए, जब कोई उपयोगकर्ता पॉइंटर को एक कंपोज़ेबल की सीमाओं से दूसरे कंपोज़ेबल पर ले जाता है, तो इवेंट को पहले कंपोज़ेबल पर भेजने के बजाय, नए कंपोज़ेबल पर भेजा जाता है.

इवेंट का इस्तेमाल

जब एक से ज़्यादा कंपोज़ेबल को जेस्चर हैंडलर असाइन किया जाता है, तो उन हैंडलर के बीच कोई टकराव नहीं होना चाहिए. उदाहरण के लिए, इस यूज़र इंटरफ़ेस (यूआई) को देखें:

सूची का आइटम, जिसमें एक इमेज, दो टेक्स्ट वाला कॉलम, और एक बटन है.

जब कोई उपयोगकर्ता बुकमार्क बटन पर टैप करता है, तो बटन का onClick लैम्ब्डा उस जेस्चर को हैंडल करता है. जब कोई उपयोगकर्ता सूची में मौजूद किसी आइटम के किसी अन्य हिस्से पर टैप करता है, तो ListItem उस जेस्चर को हैंडल करता है और लेख पर ले जाता है. पॉइंटर इनपुट के मामले में, बटन को इस इवेंट को इस्तेमाल करना चाहिए, ताकि इसके पैरंट को पता चल सके कि अब इस पर प्रतिक्रिया नहीं देनी है. पहले से मौजूद कॉम्पोनेंट में शामिल जेस्चर और सामान्य जेस्चर मॉडिफ़ायर में, इवेंट को इस्तेमाल करने का यह तरीका शामिल होता है. हालांकि, अगर आपको अपना कस्टम जेस्चर लिखना है, तो आपको इवेंट को मैन्युअल तरीके से इस्तेमाल करना होगा. PointerInputChange.consume तरीके का इस्तेमाल करके, ऐसा किया जा सकता है:

Modifier.pointerInput(Unit) {

    awaitEachGesture {
        while (true) {
            val event = awaitPointerEvent()
            // consume all changes
            event.changes.forEach { it.consume() }
        }
    }
}

किसी इवेंट को इस्तेमाल करने से, इवेंट का अन्य कंपोज़ेबल में फैलना नहीं रुकता. इसके बजाय, कंपोज़ेबल को इस्तेमाल किए गए इवेंट को साफ़ तौर पर अनदेखा करना होगा. कस्टम जेस्चर लिखते समय, आपको यह देखना चाहिए कि किसी इवेंट का इस्तेमाल पहले से ही किसी अन्य एलिमेंट ने तो नहीं किया है:

Modifier.pointerInput(Unit) {
    awaitEachGesture {
        while (true) {
            val event = awaitPointerEvent()
            if (event.changes.any { it.isConsumed }) {
                // A pointer is consumed by another gesture handler
            } else {
                // Handle unconsumed event
            }
        }
    }
}

इवेंट प्रोपेगेशन

जैसा कि पहले बताया गया है, पॉइंटर में होने वाले बदलावों को हर उस कंपोज़ेबल को पास किया जाता है जिस पर वह हिट करता है. हालांकि, अगर एक से ज़्यादा कंपोज़ेबल मौजूद हैं, तो इवेंट किस क्रम में लागू होते हैं? पिछले सेक्शन के उदाहरण के हिसाब से, यह यूज़र इंटरफ़ेस (यूआई) इस यूज़र इंटरफ़ेस (यूआई) ट्री में बदल जाता है. इसमें सिर्फ़ ListItem और Button, पॉइंटर इवेंट के लिए रिस्पॉन्स देते हैं:

ट्री स्ट्रक्चर. सबसे ऊपर की लेयर ListItem है. दूसरी लेयर में Image, Column, और Button हैं. Column को दो Text में बांटा गया है. ListItem और Button को हाइलाइट किया गया है.

पॉइंटर इवेंट, इनमें से हर कंपोज़ेबल से तीन बार गुज़रते हैं. ऐसा तीन "पास" के दौरान होता है:

  • शुरुआती पास में, इवेंट यूज़र इंटरफ़ेस (यूआई) ट्री के सबसे ऊपर से सबसे नीचे की ओर फ़्लो करता है. इस फ़्लो की मदद से, माता-पिता किसी इवेंट को बच्चे के ऐक्सेस करने से पहले ही रोक सकते हैं. उदाहरण के लिए, टूलटिप को दबाकर रखने की सुविधा को इंटरसेप्ट करना होगा. इसके बजाय, उन्हें अपने चाइल्ड व्यू को पास नहीं करना चाहिए. हमारे उदाहरण में, ListItem को Button से पहले इवेंट मिलता है.
  • Main pass में, इवेंट यूज़र इंटरफ़ेस (यूआई) ट्री के लीफ़ नोड से लेकर यूज़र इंटरफ़ेस (यूआई) ट्री के रूट तक फ़्लो करता है. इस फ़ेज़ में, आम तौर पर जेस्चर का इस्तेमाल किया जाता है. साथ ही, इवेंट को सुनते समय यह डिफ़ॉल्ट पास होता है. इस पास में जेस्चर को हैंडल करने का मतलब है कि लीफ़ नोड को उनके पैरंट नोड से ज़्यादा प्राथमिकता मिलती है. ज़्यादातर जेस्चर के लिए, यह सबसे सही तरीका है. हमारे उदाहरण में, Button को ListItem से पहले इवेंट मिलता है.
  • फ़ाइनल पास में, इवेंट यूज़र इंटरफ़ेस (यूआई) ट्री के सबसे ऊपर से लीफ़ नोड तक एक बार फिर से फ़्लो करता है. इस फ़्लो की मदद से, स्टैक में ऊपर मौजूद एलिमेंट, अपने पैरंट एलिमेंट के इवेंट इस्तेमाल करने पर जवाब दे सकते हैं. उदाहरण के लिए, जब किसी बटन को दबाने के बाद, स्क्रोल किए जा सकने वाले पैरंट को खींचकर छोड़ा जाता है, तो बटन से रिपल इफ़ेक्ट हट जाता है.

इवेंट फ़्लो को विज़ुअल तौर पर इस तरह दिखाया जा सकता है:

इनपुट में बदलाव होने के बाद, यह जानकारी फ़्लो में उस पॉइंट से आगे भेजी जाती है:

कोड में, अपनी पसंद का पास तय किया जा सकता है:

Modifier.pointerInput(Unit) {
    awaitPointerEventScope {
        val eventOnInitialPass = awaitPointerEvent(PointerEventPass.Initial)
        val eventOnMainPass = awaitPointerEvent(PointerEventPass.Main) // default
        val eventOnFinalPass = awaitPointerEvent(PointerEventPass.Final)
    }
}

इस कोड स्निपेट में, await करने के तरीके के हर कॉल से एक जैसा इवेंट मिलता है. हालांकि, इस्तेमाल से जुड़ा डेटा बदल सकता है.

जेस्चर की जांच करना

जांच के तरीकों में, performTouchInput तरीके का इस्तेमाल करके, पॉइंटर इवेंट मैन्युअल तरीके से भेजे जा सकते हैं. इससे आपको ज़्यादा लेवल के जेस्चर (जैसे, पिंच करना या दबाकर रखना) या कम लेवल के जेस्चर (जैसे, कर्सर को कुछ पिक्सल तक ले जाना) इस्तेमाल करने की सुविधा मिलती है:

composeTestRule.onNodeWithTag("MyList").performTouchInput {
    swipeUp()
    swipeDown()
    click()
}

ज़्यादा उदाहरणों के लिए, performTouchInput दस्तावेज़ देखें.

ज़्यादा जानें

Jetpack Compose में जेस्चर के बारे में ज़्यादा जानने के लिए, ये संसाधन देखें: