टैप करें और दबाएं

कई कॉम्पोज़ेबल में टैप या क्लिक के लिए पहले से सुविधा मौजूद होती है. साथ ही, इनमें एक onClick लैम्ब्डा भी शामिल होता है. उदाहरण के लिए, क्लिक किया जा सकने वाला Surface बनाया जा सकता है, जिसमें सभी Material Design के व्यवहार शामिल हों, ताकि प्लैटफ़ॉर्म के साथ इंटरैक्ट किया जा सके:

Surface(onClick = { /* handle click */ }) {
    Text("Click me!", Modifier.padding(24.dp))
}

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

हाथ के जेस्चर

ब्यौरा

टैप (या क्लिक) करें

पॉइंटर नीचे और फिर ऊपर जाता है

दो बार टैप करें

पॉइंटर नीचे, ऊपर, नीचे, ऊपर जाता है

देर तक दबाएं

पॉइंटर नीचे जाता है और थोड़े समय के लिए वहीं रहता है

प्रेस

पॉइंटर नीचे की ओर जाता है

टैप या क्लिक का जवाब देना

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

इमेज के ग्रिड की कल्पना करें, जहां उपयोगकर्ता के क्लिक करने पर इमेज फ़ुल स्क्रीन में दिखती है:

इस व्यवहार को लागू करने के लिए, ग्रिड में मौजूद हर आइटम में clickable मॉडिफ़ायर जोड़ा जा सकता है:

@Composable
private fun ImageGrid(photos: List<Photo>) {
    var activePhotoId by rememberSaveable { mutableStateOf<Int?>(null) }
    LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
        items(photos, { it.id }) { photo ->
            ImageItem(
                photo,
                Modifier.clickable { activePhotoId = photo.id }
            )
        }
    }
    if (activePhotoId != null) {
        FullScreenImage(
            photo = photos.first { it.id == activePhotoId },
            onDismiss = { activePhotoId = null }
        )
    }
}

clickable मॉडिफ़ायर, अन्य व्यवहार भी जोड़ता है:

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

काम के हिसाब से संदर्भ मेन्यू दिखाने के लिए, दबाकर रखें

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

var contextMenuPhotoId by rememberSaveable { mutableStateOf<Int?>(null) }
val haptics = LocalHapticFeedback.current
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
    items(photos, { it.id }) { photo ->
        ImageItem(
            photo,
            Modifier
                .combinedClickable(
                    onClick = { activePhotoId = photo.id },
                    onLongClick = {
                        haptics.performHapticFeedback(HapticFeedbackType.LongPress)
                        contextMenuPhotoId = photo.id
                    },
                    onLongClickLabel = stringResource(R.string.open_context_menu)
                )
        )
    }
}
if (contextMenuPhotoId != null) {
    PhotoActionsSheet(
        photo = photos.first { it.id == contextMenuPhotoId },
        onDismissSheet = { contextMenuPhotoId = null }
    )
}

सबसे सही तरीके के तौर पर, आपको उपयोगकर्ता के एलिमेंट को दबाए रखने पर, हैप्टिक फ़ीडबैक शामिल करना चाहिए. इसलिए, स्निपेट में performHapticFeedback invocaton शामिल है.

स्क्रीम पर टैप करके, किसी कॉम्पोज़ेबल को खारिज करना

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

आइए, इमेज की जानकारी वाली स्क्रीन पर नज़र डालते हैं. बैकग्राउंड में थोड़ी पारदर्शिता होनी चाहिए और ज़्यादा जानकारी वाली स्क्रीन को खारिज करने के लिए, उपयोगकर्ता उस बैकग्राउंड पर टैप कर सके:

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

@Composable
private fun Scrim(onClose: () -> Unit, modifier: Modifier = Modifier) {
    val strClose = stringResource(R.string.close)
    Box(
        modifier
            // handle pointer input
            .pointerInput(onClose) { detectTapGestures { onClose() } }
            // handle accessibility services
            .semantics(mergeDescendants = true) {
                contentDescription = strClose
                onClick {
                    onClose()
                    true
                }
            }
            // handle physical keyboard input
            .onKeyEvent {
                if (it.key == Key.Escape) {
                    onClose()
                    true
                } else {
                    false
                }
            }
            // draw scrim
            .background(Color.DarkGray.copy(alpha = 0.75f))
    )
}

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

ज़ूम करने के लिए दो बार टैप करें

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

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

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

var zoomed by remember { mutableStateOf(false) }
var zoomOffset by remember { mutableStateOf(Offset.Zero) }
Image(
    painter = rememberAsyncImagePainter(model = photo.highResUrl),
    contentDescription = null,
    modifier = modifier
        .pointerInput(Unit) {
            detectTapGestures(
                onDoubleTap = { tapOffset ->
                    zoomOffset = if (zoomed) Offset.Zero else
                        calculateOffset(tapOffset, size)
                    zoomed = !zoomed
                }
            )
        }
        .graphicsLayer {
            scaleX = if (zoomed) 2f else 1f
            scaleY = if (zoomed) 2f else 1f
            translationX = zoomOffset.x
            translationY = zoomOffset.y
        }
)