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

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

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 को शामिल किया गया है.

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

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

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

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

@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
        }
)