यूज़र इंटरफ़ेस के कॉम्पोनेंट, डिवाइस को इस्तेमाल करने वाले व्यक्ति को सुझाव देते हैं कि वे किस तरह से काम करते हैं उपयोगकर्ता इंटरैक्शन पर प्रतिक्रिया दे सकते हैं. हर कॉम्पोनेंट के लिए, रिस्पॉन्स देने का अपना तरीका होता है इंटरैक्शन होते हैं, जिनसे उपयोगकर्ता को यह जानने में मदद मिलती है कि उनके इंटरैक्शन क्या हो रहे हैं. इसके लिए उदाहरण के लिए, अगर कोई उपयोगकर्ता डिवाइस की टचस्क्रीन पर किसी बटन को छूता है, तो बटन हाइलाइट करने के लिए रंग जोड़कर दिखाया जा सकता है. यह बदलाव उपयोगकर्ता को पता चलता है कि उन्होंने बटन छू लिया है. अगर उपयोगकर्ता ऐसा नहीं करना चाहता उसे यह पता चल जाएगा कि बटन से पहले अपनी उंगली छोड़ रहे हैं--अन्यथा, बटन सक्रिय हो जाएगा.
लिखने के हाथ के जेस्चर से जुड़ा दस्तावेज़ यह बताता है कि कंपोज़र कम-लेवल वाले पॉइंटर इवेंट को हैंडल करते हैं, जैसे कि पॉइंटर मूव और क्लिक. नए तरीके से, Compose में कम लेवल के इवेंट को ऐब्सट्रैक्ट बनाया गया है हाई-लेवल इंटरैक्शन–उदाहरण के लिए, पॉइंटर इवेंट की एक सीरीज़ जुड़ने पर उस बटन को दबाकर रखें. उन उच्च-स्तरीय अमूर्तों को समझने से की मदद से यह तय किया जा सकता है कि आपका यूज़र इंटरफ़ेस (यूआई) किस तरह का उपयोगकर्ता को जवाब देगा. उदाहरण के लिए, हो सकता है कि इसका इस्तेमाल करके, यह तय किया जा सकता है कि जब कोई उपयोगकर्ता, किसी कॉम्पोनेंट से इंटरैक्ट करे, तो वह कॉम्पोनेंट कैसा दिखे या हो सकता है आप सिर्फ़ उन उपयोगकर्ता कार्रवाइयों का लॉग बनाए रखना चाहें. यह दस्तावेज़ आपको वह जानकारी देता है जिसकी ज़रूरत आपको मानक यूज़र इंटरफ़ेस (यूआई) एलिमेंट में बदलाव करने के लिए है, या अपने हिसाब से डिज़ाइन करें.
इंटरैक्शन
कई मामलों में, आपको यह जानने की ज़रूरत नहीं होती कि आपका 'लिखें' कॉम्पोनेंट कैसा है
लोगों के इंटरैक्शन को समझने में मदद करती है. उदाहरण के लिए, Button
Modifier.clickable
का इस्तेमाल करके यह पता लगाया जा सकता है कि उपयोगकर्ता ने बटन पर क्लिक किया या नहीं. अगर आप कोई सामान्य जोड़ रहे हैं
बटन का onClick
कोड तय कर सकते हैं, और
Modifier.clickable
ज़रूरत पड़ने पर उस कोड को चलाता है. इसका मतलब है कि आपको
यह जानने के लिए कि उपयोगकर्ता ने स्क्रीन पर टैप किया है या
कीबोर्ड; Modifier.clickable
से पता चलता है कि उपयोगकर्ता ने एक क्लिक किया है, और
आपका onClick
कोड चलाकर जवाब देता है.
हालांकि, अगर आपको उपयोगकर्ता के व्यवहार के हिसाब से, अपने यूज़र इंटरफ़ेस (यूआई) कॉम्पोनेंट के रिस्पॉन्स को पसंद के मुताबिक बनाना है, हो सकता है कि आपको कारोबार के बारे में और जानने की ज़रूरत हो. इस सेक्शन में ये सुविधाएं मिलती हैं कुछ जानकारी देनी होगी.
जब कोई उपयोगकर्ता, यूज़र इंटरफ़ेस (यूआई) कॉम्पोनेंट से इंटरैक्ट करता है, तो सिस्टम उसका व्यवहार दिखाता है
डाइग्नोस्टिक टूल की मदद से
Interaction
इवेंट. उदाहरण के लिए, अगर कोई उपयोगकर्ता किसी बटन को छूता है, तो बटन
PressInteraction.Press
.
अगर उपयोगकर्ता बटन के अंदर अपनी उंगली हटाता है, तो यह
PressInteraction.Release
बटन को सूचित करेगा कि क्लिक पूरा हो गया है. दूसरी ओर, अगर
जब उपयोगकर्ता अपनी उंगली को बटन के बाहर खींचकर, उंगली को वहां से हटा लेता है
जनरेट करता है
PressInteraction.Cancel
ताकि यह पता चल सके कि बटन को दबाना रद्द किया गया था, लेकिन पूरा नहीं हुआ.
ये इंटरैक्शन कोई राय नहीं हैं. इसका मतलब है कि ये निचले स्तर के इंटरैक्शन इवेंट का मकसद, उपयोगकर्ता की कार्रवाइयों या उनकी कार्रवाइयों का मतलब समझना नहीं होता क्रम. वे यह भी नहीं समझ पाते कि उपयोगकर्ता की कौन सी कार्रवाइयां प्राथमिकता मिलने की वजह से हो सकती हैं दूसरी कार्रवाइयां.
आम तौर पर, ये इंटरैक्शन दो तरीकों से शुरू होते हैं. साथ ही, शुरू और खत्म होता है. दूसरा
इंटरैक्शन में पहले वाले का संदर्भ होता है. उदाहरण के लिए, अगर कोई उपयोगकर्ता
बटन को छूता है और फिर उंगली उठाता है. टच में
PressInteraction.Press
इंटरैक्शन होता है. साथ ही, इस रिलीज़ से
PressInteraction.Release
;
Release
में एक press
प्रॉपर्टी है, जो
PressInteraction.Press
.
किसी कॉम्पोनेंट के लिए इंटरैक्शन देखने के लिए,
InteractionSource
. InteractionSource
को Kotlin की छत पर बनाया गया है
फ़्लो के लिए सेट किया गया है, ताकि आप इससे इंटरैक्शन को उसी तरह इकट्ठा कर सकें
किसी दूसरे फ़्लो के साथ काम किया जा सकता है. डिज़ाइन से जुड़े इस फ़ैसले के बारे में ज़्यादा जानकारी के लिए,
बेहतरीन इंटरैक्शन ब्लॉग पोस्ट देखें.
इंटरैक्शन स्थिति
यह भी हो सकता है कि आप अपने कॉम्पोनेंट की पहले से मौजूद सुविधाओं को भी बढ़ाना चाहें
ट्रैक करना जारी रखें. उदाहरण के लिए, हो सकता है कि आप एक बटन की सहायता से
दबाकर रखने पर रंग बदलें. इंटरैक्शन ट्रैक करने का सबसे आसान तरीका है कि
सही इंटरैक्शन स्थिति देखें. InteractionSource
एक नंबर ऑफ़र करता है
का इस्तेमाल करता है. उदाहरण के लिए, अगर
तो आप यह देखना चाहते हैं कि कोई विशेष बटन दबाया गया है या नहीं, तो आप
InteractionSource.collectIsPressedAsState()
तरीका:
val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() Button( onClick = { /* do something */ }, interactionSource = interactionSource ) { Text(if (isPressed) "Pressed!" else "Not pressed") }
collectIsPressedAsState()
के अलावा, Compose आपको
collectIsFocusedAsState()
, collectIsDraggedAsState()
, और
collectIsHoveredAsState()
. ये तरीके असल में सुविधा के तरीके हैं
निचले लेवल के InteractionSource
API पर बनाया गया है. कुछ मामलों में,
सीधे उन निचले-स्तरीय फ़ंक्शन का उपयोग करना चाहते हैं.
उदाहरण के लिए, मान लें कि आपको यह जानने की ज़रूरत है कि कोई बटन दबाया जा रहा है या नहीं, और
भी खींचें और छोड़ें या नहीं. अगर आप collectIsPressedAsState()
दोनों का इस्तेमाल करते/करती हैं
और collectIsDraggedAsState()
के साथ-साथ, Compose बहुत सारे डुप्लीकेट काम करता है, और
इस बात की कोई गारंटी नहीं है कि आपको सभी इंटरैक्शन सही क्रम में मिलेंगे. इसके लिए
ऐसी स्थितियों में, हो सकता है कि आप सीधे तौर पर
InteractionSource
. इंटरैक्शन ट्रैक करने के बारे में ज़्यादा जानकारी के लिए
आप खुद InteractionSource
के साथ काम करते हैं, InteractionSource
के साथ काम करें देखें.
इस सेक्शन में बताया गया है कि
InteractionSource
और MutableInteractionSource
.
Interaction
का इस्तेमाल करें और इसे चलाएं
InteractionSource
, Interactions
की रीड-ओनली स्ट्रीम को दिखाता है — यह
InteractionSource
में Interaction
उत्सर्जन हो सकता है. उत्सर्जन करने के लिए
Interaction
, आपको MutableInteractionSource
का इस्तेमाल करना होगा, जो
InteractionSource
.
मॉडिफ़ायर और कॉम्पोनेंट Interactions
का इस्तेमाल कर सकते हैं, उत्सर्जन कर सकते हैं या इनका इस्तेमाल और उत्सर्जन कर सकते हैं.
यहां दिए गए सेक्शन में बताया गया है कि दोनों प्लैटफ़ॉर्म पर होने वाले इंटरैक्शन का इस्तेमाल कैसे करें और
कार्रवाई बदलने वाली कुंजी और कॉम्पोनेंट.
कार्रवाई बदलने वाली कुंजी इस्तेमाल करने का उदाहरण
फ़ोकस की गई स्थिति के लिए बॉर्डर बनाने वाले मॉडिफ़ायर के लिए, आपको सिर्फ़
Interactions
, ताकि आप InteractionSource
स्वीकार कर सकें:
fun Modifier.focusBorder(interactionSource: InteractionSource): Modifier { // ... }
फ़ंक्शन हस्ताक्षर से साफ़ तौर पर पता चलता है कि यह कार्रवाई बदलने वाला एक उपभोक्ता है — यह
Interaction
s का इस्तेमाल कर सकता है, लेकिन उत्सर्जन नहीं कर सकता.
कार्रवाई बदलने वाली कुंजी का उदाहरण तैयार करना
Modifier.hoverable
जैसे होवर इवेंट को मैनेज करने वाले मॉडिफ़ायर के लिए
Interactions
उत्सर्जन करना होगा और MutableInteractionSource
को
पैरामीटर का इस्तेमाल करें:
fun Modifier.hover(interactionSource: MutableInteractionSource, enabled: Boolean): Modifier { // ... }
यह मॉडिफ़ायर प्रोड्यूसर है — यह दी गई जानकारी का इस्तेमाल कर सकता है
इस पर कर्सर घुमाने पर, HoverInteractions
उत्सर्जन करने के लिए MutableInteractionSource
या
कार्रवाई नहीं की गई.
ऐसे कॉम्पोनेंट बनाएं जो एक-दूसरे के काम आएंगे और:
मटीरियल Button
जैसे हाई-लेवल कॉम्पोनेंट, प्रोड्यूसर और
उपभोक्ताओं को सुरक्षित रखना है. ये इनपुट और फ़ोकस इवेंट को हैंडल करते हैं. साथ ही, इनके दिखने का तरीका भी बदलते हैं
इन घटनाओं के प्रतिक्रिया में सक्रिय
ऊंचाई. इस वजह से, वे MutableInteractionSource
को सीधे
पैरामीटर की मदद से, याद रखा गया इंस्टेंस दिया जा सकता है:
@Composable fun Button( onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, // exposes MutableInteractionSource as a parameter interactionSource: MutableInteractionSource? = null, elevation: ButtonElevation? = ButtonDefaults.elevatedButtonElevation(), shape: Shape = MaterialTheme.shapes.small, border: BorderStroke? = null, colors: ButtonColors = ButtonDefaults.buttonColors(), contentPadding: PaddingValues = ButtonDefaults.ContentPadding, content: @Composable RowScope.() -> Unit ) { /* content() */ }
इससे फ़ाइल फ़ोल्डर को ऊपर ले जाने
MutableInteractionSource
को कॉम्पोनेंट से बाहर रखा गया है और सभी
कॉम्पोनेंट से बने Interaction
. इसका इस्तेमाल करके, यह तय किया जा सकता है कि
या आपके यूज़र इंटरफ़ेस में किसी दूसरे कॉम्पोनेंट के दिखने का तरीका.
अगर आपको खुद के इंटरैक्टिव हाई लेवल कॉम्पोनेंट बनाने हैं, तो हमारा सुझाव है कि
आप इस तरीके से MutableInteractionSource
को पैरामीटर के तौर पर दिखाते हैं. इसके अलावा
के सबसे सही तरीकों को फ़ॉलो किया है, तो इससे पढ़ने और समझने में आसानी होती है.
किसी कॉम्पोनेंट की विज़ुअल स्थिति को उसी तरह कंट्रोल करें जैसे किसी दूसरे तरह के कॉम्पोनेंट
स्थिति (जैसे कि चालू होने की स्थिति) को पढ़ा और कंट्रोल किया जा सकता है.
कंपोज़ कई लेयर वाली आर्किटेक्चरल अप्रोच का इस्तेमाल करके, कंपोज़ की सुविधा इस्तेमाल की जाती है.
इसलिए, अच्छी क्वालिटी के मटीरियल कॉम्पोनेंट, बुनियादी इमारत के ऊपर बनाए जाते हैं
ऐसे ब्लॉक जो रिपल और अन्य कंट्रोल के लिए Interaction
s जनरेट करते हैं
विज़ुअल इफ़ेक्ट. फ़ाउंडेशन लाइब्रेरी से, हाई-लेवल इंटरैक्शन मॉडिफ़ायर मिलते हैं
जैसे कि Modifier.hoverable
, Modifier.focusable
, और
Modifier.draggable
.
होवर इवेंट का जवाब देने वाला कॉम्पोनेंट बनाने के लिए,
Modifier.hoverable
और MutableInteractionSource
को पैरामीटर के तौर पर पास करें.
कॉम्पोनेंट पर कर्सर घुमाने पर, यह HoverInteraction
उत्सर्जन करता है. इसका इस्तेमाल किया जा सकता है
इसे बदला जा सकता है.
// This InteractionSource will emit hover interactions val interactionSource = remember { MutableInteractionSource() } Box( Modifier .size(100.dp) .hoverable(interactionSource = interactionSource), contentAlignment = Alignment.Center ) { Text("Hello!") }
इस कॉम्पोनेंट पर फ़ोकस करने के लिए, Modifier.focusable
जोड़ें और पास
पैरामीटर के तौर पर एक जैसा MutableInteractionSource
. अब, दोनों
HoverInteraction.Enter/Exit
और FocusInteraction.Focus/Unfocus
उत्सर्जन किए गए
उसी MutableInteractionSource
से और आप
एक ही स्थान पर दोनों तरह के इंटरैक्शन की मौजूदगी:
// This InteractionSource will emit hover and focus interactions val interactionSource = remember { MutableInteractionSource() } Box( Modifier .size(100.dp) .hoverable(interactionSource = interactionSource) .focusable(interactionSource = interactionSource), contentAlignment = Alignment.Center ) { Text("Hello!") }
Modifier.clickable
, इससे ज़्यादा है
hoverable
और focusable
की तुलना में लेवल ऐब्स्ट्रक्शन — किसी कॉम्पोनेंट के लिए
क्लिक किया जा सकता है, यह पूरी तरह से होवर किया जा सकता है. साथ ही, जिन कॉम्पोनेंट पर क्लिक किया जा सकता है उन्हें
फ़ोकस करने में भी मदद मिलती है. Modifier.clickable
का इस्तेमाल करके, ऐसा कॉम्पोनेंट बनाया जा सकता है जो
इस पर कर्सर घुमाने से, फ़ोकस करने, और प्रेस इंटरैक्शन को मैनेज करने की सुविधा मिलती है. इसके लिए, कर्सर को नीचे की ओर वाले हिस्से से कंट्रोल करने की ज़रूरत नहीं होती
लेवल एपीआई का इस्तेमाल करें. अगर आपको अपने कॉम्पोनेंट को क्लिक करने लायक भी बनाना है, तो
hoverable
और focusable
को clickable
से बदलें:
// This InteractionSource will emit hover, focus, and press interactions val interactionSource = remember { MutableInteractionSource() } Box( Modifier .size(100.dp) .clickable( onClick = {}, interactionSource = interactionSource, // Also show a ripple effect indication = ripple() ), contentAlignment = Alignment.Center ) { Text("Hello!") }
InteractionSource
के साथ काम करें
अगर आपको किसी कॉम्पोनेंट के साथ होने वाले इंटरैक्शन के बारे में कम लेवल की जानकारी चाहिए, तो
उस कॉम्पोनेंट के InteractionSource
के लिए, स्टैंडर्ड flow API का इस्तेमाल करें.
उदाहरण के लिए, मान लीजिए कि आप प्रेस और ड्रैग की एक सूची बनाए रखना चाहते हैं
InteractionSource
के लिए इंटरैक्शन. यह कोड आधा काम करता है.
सूची में कोई नया आइटम आने पर, उसे तुरंत हाइलाइट किया जाता है:
val interactionSource = remember { MutableInteractionSource() } val interactions = remember { mutableStateListOf<Interaction>() } LaunchedEffect(interactionSource) { interactionSource.interactions.collect { interaction -> when (interaction) { is PressInteraction.Press -> { interactions.add(interaction) } is DragInteraction.Start -> { interactions.add(interaction) } } } }
हालांकि, नए इंटरैक्शन जोड़ने के अलावा, आपको इंटरैक्शन भी हटाने होंगे जब उनकी खोज बंद हो जाए (उदाहरण के लिए, जब उपयोगकर्ता कॉम्पोनेंट). यह करना आसान है, क्योंकि अंतिम इंटरैक्शन के साथ हमेशा संदर्भ देता है. यह कोड दिखाता है कि खत्म हो चुके इंटरैक्शन:
val interactionSource = remember { MutableInteractionSource() } val interactions = remember { mutableStateListOf<Interaction>() } LaunchedEffect(interactionSource) { interactionSource.interactions.collect { interaction -> when (interaction) { is PressInteraction.Press -> { interactions.add(interaction) } is PressInteraction.Release -> { interactions.remove(interaction.press) } is PressInteraction.Cancel -> { interactions.remove(interaction.press) } is DragInteraction.Start -> { interactions.add(interaction) } is DragInteraction.Stop -> { interactions.remove(interaction.start) } is DragInteraction.Cancel -> { interactions.remove(interaction.start) } } } }
अब, अगर आपको यह जानना है कि कॉम्पोनेंट को अभी दबाया जा रहा है या ड्रैग किया जा रहा है,
आपको बस यह देखना है कि interactions
खाली है या नहीं:
val isPressedOrDragged = interactions.isNotEmpty()
अगर आप यह जानना चाहते हैं कि हाल ही में क्या बातचीत हुई थी, तो आखिरी आइटम सूची में शामिल करें. उदाहरण के लिए, लिखें कि रिपल लागू करने का तरीका क्या है सबसे हाल के इंटरैक्शन के लिए इस्तेमाल किए जाने वाले सही ओवरले का पता लगाता है:
val lastInteraction = when (interactions.lastOrNull()) { is DragInteraction.Start -> "Dragged" is PressInteraction.Press -> "Pressed" else -> "No state" }
सभी Interaction
एक ही स्ट्रक्चर को फ़ॉलो करते हैं. इसलिए, आपके पास
अलग-अलग तरह के उपयोगकर्ता इंटरैक्शन के साथ काम करते समय कोड में अंतर —
पैटर्न समान है.
ध्यान दें कि इस सेक्शन में दिए गए उदाहरण,Flow
State
का इस्तेमाल करने वाले इंटरैक्शन
— इससे अपडेट की गई वैल्यू को आसानी से देखा जा सकता है,
क्योंकि स्टेट वैल्यू को पढ़ने से अपने-आप रीकंपोज़िशन बन जाएंगी. हालांकि,
कंपोज़िशन प्री-फ़्रेम बैच किया गया है. इसका मतलब है कि अगर राज्य में बदलाव होता है और
फिर उसी फ़्रेम में वापस बदल जाता है, तो स्थिति को देखने वाले कॉम्पोनेंट
बदलाव देखें.
यह इंटरैक्शन के लिए ज़रूरी है, क्योंकि इंटरैक्शन नियमित रूप से शुरू और खत्म हो सकते हैं
एक ही फ़्रेम में आएँ. उदाहरण के लिए, Button
के साथ पिछले उदाहरण का इस्तेमाल करके:
val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() Button(onClick = { /* do something */ }, interactionSource = interactionSource) { Text(if (isPressed) "Pressed!" else "Not pressed") }
अगर कोई मीडिया एक ही फ़्रेम में शुरू और खत्म होता है, तो टेक्स्ट कभी भी इस तरह नहीं दिखेगा
"दबा हुआ!". ज़्यादातर मामलों में, यह कोई समस्या नहीं है —
थोड़ा समय लेने पर, झिलमिलाहट:
उपयोगकर्ता को आसानी होती है. कुछ मामलों में, जैसे कि रिपल इफ़ेक्ट या
समान एनिमेशन, तो हो सकता है कि आप प्रभाव को कम से कम
एक सीमा तक दिखाना चाहें
यह सुविधा, समय के साथ बंद हो सकती है. यह बटन अब बटन नहीं दबाया जाता है. यहां की यात्रा पर हूं
ऐसा करने पर, कलेक्शन में मौजूद ऐनिमेशन को सीधे तौर पर चालू और बंद किया जा सकता है
Labda पर सेट करें. यहां इस पैटर्न का एक उदाहरण दिया गया है:
ऐनिमेट किए गए बॉर्डर के साथ बेहतर Indication
बनाएं सेक्शन.
उदाहरण: कस्टम इंटरैक्शन हैंडलिंग के साथ कॉम्पोनेंट बनाएं
यह देखने के लिए कि इनपुट के लिए कस्टम रिस्पॉन्स के साथ कॉम्पोनेंट कैसे बनाए जा सकते हैं, यहां बदले हुए बटन का उदाहरण. इस मामले में, मान लें कि आपको ऐसा बटन चाहिए जो दबाए जाने पर प्रतिक्रिया देने के लिए, इसका रंग-रूप बदल कर देखें:
ऐसा करने के लिए, Button
के आधार पर कस्टम कंपोज़ेबल बनाएं. साथ ही,
आइकन बनाने के लिए अतिरिक्त icon
पैरामीटर (इस मामले में, एक शॉपिंग कार्ट). आपने लोगों तक पहुंचाया मुफ़्त में
collectIsPressedAsState()
को कॉल करके ट्रैक करें कि उपयोगकर्ता
बटन; होने पर, आपको आइकॉन जोड़ना होगा. यहां बताया गया है कि कोड कैसा दिखता है:
@Composable fun PressIconButton( onClick: () -> Unit, icon: @Composable () -> Unit, text: @Composable () -> Unit, modifier: Modifier = Modifier, interactionSource: MutableInteractionSource? = null ) { val isPressed = interactionSource?.collectIsPressedAsState()?.value ?: false Button( onClick = onClick, modifier = modifier, interactionSource = interactionSource ) { AnimatedVisibility(visible = isPressed) { if (isPressed) { Row { icon() Spacer(Modifier.size(ButtonDefaults.IconSpacing)) } } } text() } }
नए कंपोज़ेबल का इस्तेमाल इस तरह किया जा सकता है:
PressIconButton( onClick = {}, icon = { Icon(Icons.Filled.ShoppingCart, contentDescription = null) }, text = { Text("Add to cart") } )
क्योंकि इस नया PressIconButton
को मौजूदा मटीरियल के आधार पर बनाया गया है
Button
, यह उपयोगकर्ता के इंटरैक्शन पर सामान्य तौर पर प्रतिक्रिया करता है. जब उपयोगकर्ता
बटन को दबाता है, लेकिन इसकी ओपैसिटी में थोड़ा-बहुत बदलाव होता है.
सामग्री Button
.
Indication
का इस्तेमाल करके, फिर से इस्तेमाल किया जा सकने वाला कस्टम इफ़ेक्ट बनाएं और उसे लागू करें
पिछले सेक्शन में, आपने रिस्पॉन्स के तौर पर किसी कॉम्पोनेंट के किसी हिस्से को बदलने का तरीका बताया था
अलग-अलग Interaction
के लिए, जैसे कि दबाए जाने पर कोई आइकॉन दिखाना. यह एक जैसा
अप्रोच का इस्तेमाल करके, आपके दिए गए पैरामीटर की वैल्यू को
या कॉम्पोनेंट के अंदर दिखने वाले कॉन्टेंट में बदलाव कर सकता है, लेकिन ऐसा हो सकता है
यह सिर्फ़ हर कॉम्पोनेंट के हिसाब से लागू होता है. अक्सर, किसी ऐप्लिकेशन या डिज़ाइन सिस्टम को
में स्टेटफ़ुल विज़ुअल इफ़ेक्ट के लिए एक सामान्य सिस्टम होगा.
सभी कॉम्पोनेंट पर एक तरह से लागू किया जाना चाहिए.
अगर आपको इस तरह का डिज़ाइन सिस्टम बनाना है, तो एक कॉम्पोनेंट को पसंद के मुताबिक बनाना और अन्य कॉम्पोनेंट के लिए इस कस्टमाइज़ेशन का दोबारा इस्तेमाल करना, पब्लिशर के लिए मुश्किल हो सकता है ये वजहें हो सकती हैं:
- डिज़ाइन सिस्टम के हर कॉम्पोनेंट में एक ही बॉयलरप्लेट की ज़रूरत होती है
- इस इफ़ेक्ट को, नए कॉम्पोनेंट और कस्टम कॉन्फ़िगरेशन पर लागू करना बहुत आसान है क्लिक किए जा सकने वाले कॉम्पोनेंट
- कस्टम इफ़ेक्ट को दूसरे इफ़ेक्ट के साथ इस्तेमाल करना मुश्किल हो सकता है
इन समस्याओं से बचने और अपने पूरे सिस्टम में कस्टम कॉम्पोनेंट को आसानी से स्केल करने के लिए,
तो Indication
का इस्तेमाल किया जा सकता है.
Indication
फिर से इस्तेमाल किया जा सकने वाला विज़ुअल इफ़ेक्ट दिखाता है, जिसे सभी पर लागू किया जा सकता है
कॉम्पोनेंट, ऐप्लिकेशन या डिज़ाइन सिस्टम में मौजूद होते हैं. Indication
को दो हिस्सों में बांटा गया है
वाहन के पुर्ज़े:
IndicationNodeFactory
: ऐसी फ़ैक्ट्री जोModifier.Node
इंस्टेंस बनाती है किसी कॉम्पोनेंट के लिए विज़ुअल इफ़ेक्ट रेंडर करने में मदद मिलती है. आसानी से लागू करने के लिए, जो अलग-अलग कॉम्पोनेंट में बदलाव करता है, तो यह सिंगलटन (ऑब्जेक्ट) हो सकता है और पूरे कॉम्पोनेंट में फिर से इस्तेमाल किया जा सकता है को ट्रैक करने की सुविधा मिलती है.ये इंस्टेंस, स्टेटफ़ुल या स्टेटलेस हो सकते हैं. चूंकि वे प्रति घंटे बनाए जाते हैं कॉम्पोनेंट, वे
CompositionLocal
से वैल्यू वापस पा सकते हैं. इससे यह तय हो पाएगा कि वे किसी खास कॉम्पोनेंट के अंदर दिखते या काम करते हैं, जैसा कि किसी दूसरे कॉम्पोनेंट में होता हैModifier.Node
.Modifier.indication
: ऐसा मॉडिफ़ायर जोIndication
कॉम्पोनेंट.Modifier.clickable
और अन्य हाई लेवल इंटरैक्शन मॉडिफ़ायर संकेत पैरामीटर को सीधे स्वीकार करते हैं, ताकि वे न सिर्फ़Interaction
s. हालांकि, येInteraction
के लिए विज़ुअल इफ़ेक्ट भी बना सकते हैं उत्सर्जन इसलिए, आसान मामलों में,Modifier.clickable
का इस्तेमाल बिनाModifier.indication
की ज़रूरत है.
इफ़ेक्ट को Indication
से बदलें
इस सेक्शन में, मैन्युअल स्केल पर लागू किए गए स्केल इफ़ेक्ट को बदलने का तरीका बताया गया है एक खास बटन, जिसके बारे में अलग-अलग तरह के संकेत मिलते हैं. इसे एक से ज़्यादा ब्राउज़र पर फिर से इस्तेमाल किया जा सकता है कॉम्पोनेंट.
यह कोड एक ऐसा बटन बनाता है, जो दबाने पर नीचे की ओर स्केल करता है:
val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() val scale by animateFloatAsState(targetValue = if (isPressed) 0.9f else 1f, label = "scale") Button( modifier = Modifier.scale(scale), onClick = { }, interactionSource = interactionSource ) { Text(if (isPressed) "Pressed!" else "Not pressed") }
ऊपर दिए गए स्निपेट में मौजूद स्केल इफ़ेक्ट को Indication
में बदलने के लिए, फ़ॉलो करें
यह तरीका अपनाएं:
स्केल इफ़ेक्ट लागू करने के लिए ज़िम्मेदार
Modifier.Node
बनाएं. अटैच होने पर, नोड पिछले इंटरैक्शन जैसा ही इंटरैक्शन सोर्स को मॉनिटर करता है उदाहरण. यहां अंतर सिर्फ़ इतना है कि यह सीधे ऐनिमेशन लॉन्च करता है वह इन इंटरैक्शन को स्थिति में बदलने के बजाय.नोड को
DrawModifierNode
लागू करना होगा, ताकि इसे बदला जा सकेContentDrawScope#draw()
और उसी ड्रॉइंग का इस्तेमाल करके स्केल इफ़ेक्ट रेंडर करें Compose में किसी भी दूसरे ग्राफ़िक्स एपीआई की तरह कमांड देते हैं.ContentDrawScope
पाने वाले की मदद से उपलब्धdrawContent()
को कॉल करने पर, वह मैसेज दिखेगा एक वास्तविक कॉम्पोनेंट जिस परIndication
लागू किया जाना चाहिए, ताकि आप इस फ़ंक्शन को स्केल ट्रांसफ़ॉर्मेशन में कॉल करना ज़रूरी है. पक्का करें किIndication
लागू करने की सुविधा की मदद से, कभी-कभीdrawContent()
को कॉल किया जाता है; ऐसा न करने पर, जिस कॉम्पोनेंट परIndication
लागू किया जा रहा है वह ड्रॉ नहीं किया जाएगा.private class ScaleNode(private val interactionSource: InteractionSource) : Modifier.Node(), DrawModifierNode { var currentPressPosition: Offset = Offset.Zero val animatedScalePercent = Animatable(1f) private suspend fun animateToPressed(pressPosition: Offset) { currentPressPosition = pressPosition animatedScalePercent.animateTo(0.9f, spring()) } private suspend fun animateToResting() { animatedScalePercent.animateTo(1f, spring()) } override fun onAttach() { coroutineScope.launch { interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> animateToPressed(interaction.pressPosition) is PressInteraction.Release -> animateToResting() is PressInteraction.Cancel -> animateToResting() } } } } override fun ContentDrawScope.draw() { scale( scale = animatedScalePercent.value, pivot = currentPressPosition ) { this@draw.drawContent() } } }
IndicationNodeFactory
बनाएं. इसकी ज़िम्मेदारी सिर्फ़ एक ऐसी कंपनी दिए गए इंटरैक्शन सोर्स के लिए नया नोड इंस्टेंस. क्योंकि इस तरह के पैरामीटर का इस्तेमाल करके, इंंडिकेटर को कॉन्फ़िगर करने के लिए पैरामीटर का इस्तेमाल किया जाता है, तो फ़ैक्ट्री एक ऑब्जेक्ट हो सकता है:object ScaleIndication : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return ScaleNode(interactionSource) } override fun equals(other: Any?): Boolean = other === ScaleIndication override fun hashCode() = 100 }
Modifier.clickable
, अंदरूनी तौर परModifier.indication
का इस्तेमाल करता है. इसलिए, इसेScaleIndication
वाला क्लिक किया जा सकने वाला कॉम्पोनेंट चुनने के लिए, आपको बस यह जानकारी देनी होगीclickable
के पैरामीटर के तौर परIndication
:Box( modifier = Modifier .size(100.dp) .clickable( onClick = {}, indication = ScaleIndication, interactionSource = null ) .background(Color.Blue), contentAlignment = Alignment.Center ) { Text("Hello!", color = Color.White) }
इससे, कस्टम टेंप्लेट का इस्तेमाल करके, हाई लेवल के फिर से इस्तेमाल किए जा सकने वाले कॉम्पोनेंट बनाना भी आसान हो जाता है
Indication
— बटन ऐसा दिख सकता है:@Composable fun ScaleButton( onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, interactionSource: MutableInteractionSource? = null, shape: Shape = CircleShape, content: @Composable RowScope.() -> Unit ) { Row( modifier = modifier .defaultMinSize(minWidth = 76.dp, minHeight = 48.dp) .clickable( enabled = enabled, indication = ScaleIndication, interactionSource = interactionSource, onClick = onClick ) .border(width = 2.dp, color = Color.Blue, shape = shape) .padding(horizontal = 16.dp, vertical = 8.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, content = content ) }
इसके बाद, बटन का इस्तेमाल इस तरह किया जा सकता है:
ScaleButton(onClick = {}) { Icon(Icons.Filled.ShoppingCart, "") Spacer(Modifier.padding(10.dp)) Text(text = "Add to cart!") }
ऐनिमेशन वाले बॉर्डर के साथ बेहतर Indication
बनाएं
Indication
सिर्फ़ ट्रांसफ़ॉर्मेशन इफ़ेक्ट तक सीमित नहीं है. जैसे,
कॉम्पोनेंट. IndicationNodeFactory
, Modifier.Node
दिखाता है. इसलिए, आपके पास ड्रॉ करने का विकल्प है
अन्य ड्रॉइंग एपीआई की तरह, कॉन्टेंट के ऊपर या नीचे किसी भी तरह का इफ़ेक्ट होता है. इसके लिए
उदाहरण के लिए, कॉम्पोनेंट के चारों ओर ऐनिमेटेड बॉर्डर बनाया जा सकता है और
कॉम्पोनेंट को दबाने पर उसके ऊपर:
यहां लागू किया गया Indication
, काफ़ी हद तक पिछले उदाहरण से मिलता-जुलता है —
यह बस कुछ पैरामीटर के साथ एक नोड बनाता है. चूंकि एनिमेटेड बॉर्डर निर्भर करता है
जिस कॉम्पोनेंट के लिए Indication
का इस्तेमाल किया जाता है उसके आकार और बॉर्डर पर,
Indication
लागू करने के लिए, आकार और बॉर्डर की चौड़ाई भी देना ज़रूरी है
पैरामीटर के रूप में:
data class NeonIndication(private val shape: Shape, private val borderWidth: Dp) : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return NeonNode( shape, // Double the border size for a stronger press effect borderWidth * 2, interactionSource ) } }
Modifier.Node
को लागू करने का सिद्धांत भी एक जैसा ही है, भले ही
ड्रॉइंग कोड बनाना ज़्यादा मुश्किल है. पहले की तरह ही, यह InteractionSource
के आस-पास रहता है
अटैच करने पर, ऐनिमेशन लॉन्च करता है, और ड्रॉ करने के लिए DrawModifierNode
लागू करता है
कॉन्टेंट पर पड़ने वाला असर:
private class NeonNode( private val shape: Shape, private val borderWidth: Dp, private val interactionSource: InteractionSource ) : Modifier.Node(), DrawModifierNode { var currentPressPosition: Offset = Offset.Zero val animatedProgress = Animatable(0f) val animatedPressAlpha = Animatable(1f) var pressedAnimation: Job? = null var restingAnimation: Job? = null private suspend fun animateToPressed(pressPosition: Offset) { // Finish any existing animations, in case of a new press while we are still showing // an animation for a previous one restingAnimation?.cancel() pressedAnimation?.cancel() pressedAnimation = coroutineScope.launch { currentPressPosition = pressPosition animatedPressAlpha.snapTo(1f) animatedProgress.snapTo(0f) animatedProgress.animateTo(1f, tween(450)) } } private fun animateToResting() { restingAnimation = coroutineScope.launch { // Wait for the existing press animation to finish if it is still ongoing pressedAnimation?.join() animatedPressAlpha.animateTo(0f, tween(250)) animatedProgress.snapTo(0f) } } override fun onAttach() { coroutineScope.launch { interactionSource.interactions.collect { interaction -> when (interaction) { is PressInteraction.Press -> animateToPressed(interaction.pressPosition) is PressInteraction.Release -> animateToResting() is PressInteraction.Cancel -> animateToResting() } } } } override fun ContentDrawScope.draw() { val (startPosition, endPosition) = calculateGradientStartAndEndFromPressPosition( currentPressPosition, size ) val brush = animateBrush( startPosition = startPosition, endPosition = endPosition, progress = animatedProgress.value ) val alpha = animatedPressAlpha.value drawContent() val outline = shape.createOutline(size, layoutDirection, this) // Draw overlay on top of content drawOutline( outline = outline, brush = brush, alpha = alpha * 0.1f ) // Draw border on top of overlay drawOutline( outline = outline, brush = brush, alpha = alpha, style = Stroke(width = borderWidth.toPx()) ) } /** * Calculates a gradient start / end where start is the point on the bounding rectangle of * size [size] that intercepts with the line drawn from the center to [pressPosition], * and end is the intercept on the opposite end of that line. */ private fun calculateGradientStartAndEndFromPressPosition( pressPosition: Offset, size: Size ): Pair<Offset, Offset> { // Convert to offset from the center val offset = pressPosition - size.center // y = mx + c, c is 0, so just test for x and y to see where the intercept is val gradient = offset.y / offset.x // We are starting from the center, so halve the width and height - convert the sign // to match the offset val width = (size.width / 2f) * sign(offset.x) val height = (size.height / 2f) * sign(offset.y) val x = height / gradient val y = gradient * width // Figure out which intercept lies within bounds val intercept = if (abs(y) <= abs(height)) { Offset(width, y) } else { Offset(x, height) } // Convert back to offsets from 0,0 val start = intercept + size.center val end = Offset(size.width - start.x, size.height - start.y) return start to end } private fun animateBrush( startPosition: Offset, endPosition: Offset, progress: Float ): Brush { if (progress == 0f) return TransparentBrush // This is *expensive* - we are doing a lot of allocations on each animation frame. To // recreate a similar effect in a performant way, it would be better to create one large // gradient and translate it on each frame, instead of creating a whole new gradient // and shader. The current approach will be janky! val colorStops = buildList { when { progress < 1 / 6f -> { val adjustedProgress = progress * 6f add(0f to Blue) add(adjustedProgress to Color.Transparent) } progress < 2 / 6f -> { val adjustedProgress = (progress - 1 / 6f) * 6f add(0f to Purple) add(adjustedProgress * MaxBlueStop to Blue) add(adjustedProgress to Blue) add(1f to Color.Transparent) } progress < 3 / 6f -> { val adjustedProgress = (progress - 2 / 6f) * 6f add(0f to Pink) add(adjustedProgress * MaxPurpleStop to Purple) add(MaxBlueStop to Blue) add(1f to Blue) } progress < 4 / 6f -> { val adjustedProgress = (progress - 3 / 6f) * 6f add(0f to Orange) add(adjustedProgress * MaxPinkStop to Pink) add(MaxPurpleStop to Purple) add(MaxBlueStop to Blue) add(1f to Blue) } progress < 5 / 6f -> { val adjustedProgress = (progress - 4 / 6f) * 6f add(0f to Yellow) add(adjustedProgress * MaxOrangeStop to Orange) add(MaxPinkStop to Pink) add(MaxPurpleStop to Purple) add(MaxBlueStop to Blue) add(1f to Blue) } else -> { val adjustedProgress = (progress - 5 / 6f) * 6f add(0f to Yellow) add(adjustedProgress * MaxYellowStop to Yellow) add(MaxOrangeStop to Orange) add(MaxPinkStop to Pink) add(MaxPurpleStop to Purple) add(MaxBlueStop to Blue) add(1f to Blue) } } } return linearGradient( colorStops = colorStops.toTypedArray(), start = startPosition, end = endPosition ) } companion object { val TransparentBrush = SolidColor(Color.Transparent) val Blue = Color(0xFF30C0D8) val Purple = Color(0xFF7848A8) val Pink = Color(0xFFF03078) val Orange = Color(0xFFF07800) val Yellow = Color(0xFFF0D800) const val MaxYellowStop = 0.16f const val MaxOrangeStop = 0.33f const val MaxPinkStop = 0.5f const val MaxPurpleStop = 0.67f const val MaxBlueStop = 0.83f } }
मुख्य अंतर यह है कि अब
animateToResting()
फ़ंक्शन के साथ ऐनिमेशन का इस्तेमाल किया जाता है, ताकि भले ही प्रेस
तुरंत रिलीज़ किया जाएगा, तो प्रेस ऐनिमेशन जारी रहेगा. ऐसी और भी कई ज़रूरी कंपनियां
animateToPressed
की शुरुआत में कई बार तेज़ी से दबाने के लिए — अगर एक बार दबाया जाए
मौजूदा प्रेस या आराम के समय के ऐनिमेशन के दौरान होता है, तो पिछला ऐनिमेशन
रद्द कर दिया गया है, और प्रेस ऐनिमेशन फिर से शुरू हो रहा है. एक साथ कई डिवाइसों पर काम करने के लिए
समवर्ती प्रभाव (जैसे कि रिपल के साथ, जिसमें नया रिपल ऐनिमेशन
के अलावा, अन्य तरंगों से ऊपर भी टाइप करते हैं, तो आप किसी भी समय एक सूची में एनिमेशन
मौजूदा ऐनिमेशन को रद्द करने और नए ऐनिमेशन शुरू करने के लिए किया जा सकता है.
आपके लिए सुझाव
- ध्यान दें: JavaScript बंद होने पर लिंक टेक्स्ट दिखता है
- हाथ के जेस्चर (हाव-भाव) के बारे में जानकारी
- Jetpack Compose के लिए Kotlin
- मटीरियल कॉम्पोनेंट और लेआउट