एक नज़र में जानकारी दिखाने वाला यूज़र इंटरफ़ेस (यूआई) बनाएं

इस पेज पर, Glance के मौजूदा कॉम्पोनेंट का इस्तेमाल करके, Glance में साइज़ मैनेज करने और फ़्लेक्सिबल और रिस्पॉन्सिव लेआउट उपलब्ध कराने का तरीका बताया गया है.

Box, Column, और Row का इस्तेमाल करें

Glance में तीन मुख्य कंपोज़ेबल लेआउट होते हैं:

  • Box: एक एलिमेंट को दूसरे एलिमेंट के ऊपर रखता है. यह RelativeLayout में बदल जाता है.

  • Column: वर्टिकल ऐक्सिस में एलिमेंट को एक के बाद एक रखता है. यह वर्टिकल ओरिएंटेशन वाले LinearLayout में बदल जाता है.

  • Row: यह हॉरिज़ॉन्टल ऐक्सिस में एलिमेंट को एक के बाद एक रखता है. यह हॉरिज़ॉन्टल ओरिएंटेशन वाली LinearLayout में बदल जाता है.

Glance, Scaffold ऑब्जेक्ट के साथ काम करता है. अपने Column, Row, और Box कंपोज़ेबल को दिए गए Scaffold ऑब्जेक्ट में रखें.

कॉलम, लाइन, और बॉक्स लेआउट.
पहली इमेज. कॉलम, लाइन, और बॉक्स वाले लेआउट के उदाहरण.

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

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

Row(modifier = GlanceModifier.fillMaxWidth().padding(16.dp)) {
    val modifier = GlanceModifier.defaultWeight()
    Text("first", modifier)
    Text("second", modifier)
    Text("third", modifier)
}

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

स्क्रोल किए जा सकने वाले लेआउट का इस्तेमाल करना

रिस्पॉन्सिव कॉन्टेंट उपलब्ध कराने का एक और तरीका है कि उसे स्क्रोल किया जा सके. LazyColumn कंपोज़ेबल की मदद से ऐसा किया जा सकता है. इस कंपोज़ेबल की मदद से, ऐप्लिकेशन विजेट में स्क्रोल किए जा सकने वाले कंटेनर में दिखाए जाने वाले आइटम का सेट तय किया जा सकता है.

यहां दिए गए स्निपेट में, LazyColumn के अंदर आइटम तय करने के अलग-अलग तरीके दिखाए गए हैं.

आपके पास आइटम की संख्या बताने का विकल्प होता है:

// Remember to import Glance Composables
// import androidx.glance.appwidget.layout.LazyColumn

LazyColumn {
    items(10) { index: Int ->
        Text(
            text = "Item $index",
            modifier = GlanceModifier.fillMaxWidth()
        )
    }
}

अलग-अलग आइटम उपलब्ध कराएं:

LazyColumn {
    item {
        Text("First Item")
    }
    item {
        Text("Second Item")
    }
}

आइटम की सूची या ऐरे दें:

LazyColumn {
    items(peopleNameList) { name ->
        Text(name)
    }
}

ऊपर दिए गए उदाहरणों को मिलाकर भी इस्तेमाल किया जा सकता है:

LazyColumn {
    item {
        Text("Names:")
    }
    items(peopleNameList) { name ->
        Text(name)
    }

    // or in case you need the index:
    itemsIndexed(peopleNameList) { index, person ->
        Text("$person at index $index")
    }
}

ध्यान दें कि पिछले स्निपेट में itemId के बारे में नहीं बताया गया है. itemId तय करने से, परफ़ॉर्मेंस को बेहतर बनाने और Android 12 और इसके बाद के वर्शन में, सूची और appWidget के अपडेट के दौरान स्क्रोल करने की जगह को बनाए रखने में मदद मिलती है. उदाहरण के लिए, सूची में आइटम जोड़ते या हटाते समय. यहां दिए गए उदाहरण में, itemId को तय करने का तरीका बताया गया है:

items(items = peopleList, itemId = { person -> person.id.hashCode().toLong() }) { person ->
    Text(person.name)
}

स्नैप स्क्रोलिंग

स्नैप स्क्रोलिंग एक ऐनिमेशन है. इसकी मदद से, स्क्रोल किए जा सकने वाले कॉन्टेंट को विजेट कंटेनर के सबसे ऊपर स्नैप किया जा सकता है.

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


स्नैप स्क्रोलिंग की सुविधा लागू करने के लिए, पक्का करें कि आपने ये शर्तें पूरी की हों:

  • Glance की डिपेंडेंसी को 1.3.0-alpha02 या इसके बाद के वर्शन पर अपडेट करें.
  • अपने compileSdk को 37 या इससे ज़्यादा पर सेट करें, क्योंकि स्नैप स्क्रोलिंग की सुविधा, Android 17 और इसके बाद के वर्शन वाले डिवाइसों पर काम करती है.
  • VerticalScrollMode का इस्तेमाल करके, LazyColumn को कॉन्फ़िगर करें. अगर डिवाइस पर स्नैप स्क्रोलिंग की सुविधा काम करती है, तो SnapScrollMatchHeight का इस्तेमाल करें. अगर ऐसा नहीं है, तो Normal का इस्तेमाल करें.
लेख पढ़ें.

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

@Composable
fun SnapScrollLayout() {
    val height = LocalSize.current.height
    val items = listOf(
        ColorItem(Color.Red, "Red"),
        ColorItem(Color.Yellow, "Yellow"),
        ColorItem(Color.Blue, "Blue")
    )

    val scrollMode = if (Build.VERSION.SDK_INT >= 37) {
        VerticalScrollMode.SnapScrollMatchHeight(height)
    } else {
        VerticalScrollMode.Normal
    }

    LazyColumn(
        verticalScrollMode = scrollMode
    ) {
        items(items) { item ->
            ColorCard(item, height)
        }
    }
}

@Composable
private fun ColorCard(item: ColorItem, height: Dp) {
    Box(
        modifier = GlanceModifier
            .background(item.color)
            .fillMaxWidth()
            .height(height),
        contentAlignment = Alignment.Center
    ) {
        Text(
            text = item.name,
            modifier = GlanceModifier.background(Color.White)
        )
    }
}

SizeMode को परिभाषित करें

AppWidget साइज़, डिवाइस, उपयोगकर्ता की पसंद या लॉन्चर के हिसाब से अलग-अलग हो सकते हैं. इसलिए, विजेट के फ़्लेक्सिबल लेआउट उपलब्ध कराएं पेज पर बताए गए तरीके से, फ़्लेक्सिबल लेआउट उपलब्ध कराना ज़रूरी है. Glance, SizeMode की परिभाषा और LocalSize वैल्यू की मदद से इसे आसान बनाता है. नीचे दिए गए सेक्शन में, तीनों मोड के बारे में बताया गया है.

SizeMode.Single

SizeMode.Single डिफ़ॉल्ट मोड है. इससे पता चलता है कि सिर्फ़ एक तरह का कॉन्टेंट दिया गया है. इसका मतलब है कि AppWidget का उपलब्ध साइज़ बदलने पर भी, कॉन्टेंट का साइज़ नहीं बदलता.

class MyAppWidget : GlanceAppWidget() {

    override val sizeMode = SizeMode.Single

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // ...

        provideContent {
            MyContent()
        }
    }

    @Composable
    private fun MyContent() {
        // Size will be the minimum size or resizable
        // size defined in the App Widget metadata
        val size = LocalSize.current
        // ...
    }
}

इस मोड का इस्तेमाल करते समय, पक्का करें कि:

  • कॉन्टेंट के साइज़ के आधार पर, कम से कम और ज़्यादा से ज़्यादा साइज़ की मेटाडेटा वैल्यू सही तरीके से तय की गई हों.
  • कॉन्टेंट, साइज़ की अनुमानित रेंज में काफ़ी हद तक फ़िट हो जाता है.

आम तौर पर, आपको इस मोड का इस्तेमाल तब करना चाहिए, जब:

a) AppWidget का साइज़ तय हो या b) साइज़ बदलने पर, उसका कॉन्टेंट न बदले.

SizeMode.Responsive

यह मोड, रिस्पॉन्सिव लेआउट उपलब्ध कराने के बराबर है. इससे GlanceAppWidget को, खास साइज़ के हिसाब से रिस्पॉन्सिव लेआउट का सेट तय करने की अनुमति मिलती है. हर तय किए गए साइज़ के लिए, कॉन्टेंट बनाया जाता है. साथ ही, जब AppWidget बनाया या अपडेट किया जाता है, तब कॉन्टेंट को उस साइज़ के साथ मैप किया जाता है. इसके बाद, सिस्टम उपलब्ध साइज़ के हिसाब से सबसे सही विकल्प चुनता है.

उदाहरण के लिए, हमारे डेस्टिनेशन AppWidget में, तीन साइज़ और उनके कॉन्टेंट तय किए जा सकते हैं:

class MyAppWidget : GlanceAppWidget() {

    companion object {
        private val SMALL_SQUARE = DpSize(100.dp, 100.dp)
        private val HORIZONTAL_RECTANGLE = DpSize(250.dp, 100.dp)
        private val BIG_SQUARE = DpSize(250.dp, 250.dp)
    }

    override val sizeMode = SizeMode.Responsive(
        setOf(
            SMALL_SQUARE,
            HORIZONTAL_RECTANGLE,
            BIG_SQUARE
        )
    )

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // ...

        provideContent {
            MyContent()
        }
    }

    @Composable
    private fun MyContent() {
        // Size will be one of the sizes defined above.
        val size = LocalSize.current
        Column {
            if (size.height >= BIG_SQUARE.height) {
                Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp))
            }
            Row(horizontalAlignment = Alignment.CenterHorizontally) {
                Button()
                Button()
                if (size.width >= HORIZONTAL_RECTANGLE.width) {
                    Button("School")
                }
            }
            if (size.height >= BIG_SQUARE.height) {
                Text(text = "provided by X")
            }
        }
    }
}

पिछले उदाहरण में, provideContent तरीके को तीन बार कॉल किया गया है और इसे तय किए गए साइज़ पर मैप किया गया है.

  • पहले कॉल में, साइज़ का आकलन 100x100 के तौर पर किया जाता है. कॉन्टेंट में न तो अतिरिक्त बटन शामिल है और न ही ऊपर और नीचे मौजूद टेक्स्ट.
  • दूसरे कॉल में, साइज़ 250x100 के तौर पर दिखता है. कॉन्टेंट में अतिरिक्त बटन शामिल है, लेकिन ऊपर और नीचे मौजूद टेक्स्ट शामिल नहीं है.
  • तीसरे कॉल में, साइज़ का आकलन 250x250 के तौर पर किया जाता है. कॉन्टेंट में अतिरिक्त बटन और दोनों टेक्स्ट शामिल हैं.

SizeMode.Responsive, अन्य दो मोड का कॉम्बिनेशन है. इससे आपको पहले से तय की गई सीमाओं के अंदर, रिस्पॉन्सिव कॉन्टेंट तय करने की सुविधा मिलती है. आम तौर पर, इस मोड से बेहतर परफ़ॉर्मेंस मिलती है. साथ ही, AppWidget का साइज़ बदलने पर, ट्रांज़िशन आसानी से हो पाते हैं.

नीचे दी गई टेबल में, SizeMode और AppWidget के आधार पर साइज़ की वैल्यू दिखाई गई है:

उपलब्ध साइज़ 105 x 110 203 x 112 72 x 72 203 x 150
SizeMode.Single 110 x 110 110 x 110 110 x 110 110 x 110
SizeMode.Exact 105 x 110 203 x 112 72 x 72 203 x 150
SizeMode.Responsive 80 x 100 80 x 100 80 x 100 150 x 120
* सटीक वैल्यू सिर्फ़ डेमो के लिए हैं.

SizeMode.Exact

SizeMode.Exact का मतलब लेआउट की सटीक जानकारी देना है. इससे GlanceAppWidget कॉन्टेंट के लिए हर बार अनुरोध किया जाता है, जब उपलब्ध AppWidget का साइज़ बदलता है. उदाहरण के लिए, जब उपयोगकर्ता होम स्क्रीन पर AppWidget का साइज़ बदलता है.

उदाहरण के लिए, डेस्टिनेशन विजेट में एक और बटन जोड़ा जा सकता है. ऐसा तब होता है, जब उपलब्ध चौड़ाई किसी तय वैल्यू से ज़्यादा हो.

class MyAppWidget : GlanceAppWidget() {

    override val sizeMode = SizeMode.Exact

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // ...

        provideContent {
            MyContent()
        }
    }

    @Composable
    private fun MyContent() {
        // Size will be the size of the AppWidget
        val size = LocalSize.current
        Column {
            Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp))
            Row(horizontalAlignment = Alignment.CenterHorizontally) {
                Button()
                Button()
                if (size.width > 250.dp) {
                    Button("School")
                }
            }
        }
    }
}

यह मोड, अन्य मोड की तुलना में ज़्यादा विकल्प देता है. हालांकि, इसमें कुछ चेतावनियां शामिल हैं:

  • साइज में बदलाव होने पर, AppWidget को पूरी तरह से फिर से बनाना होगा. कॉन्टेंट जटिल होने पर, इससे परफ़ॉर्मेंस से जुड़ी समस्याएं हो सकती हैं और यूज़र इंटरफ़ेस (यूआई) में बदलाव हो सकते हैं.
  • लॉन्चर के हिसाब से, विजेट का साइज़ अलग-अलग हो सकता है. उदाहरण के लिए, अगर लॉन्चर साइज़ की सूची नहीं देता है, तो सबसे छोटा साइज़ इस्तेमाल किया जाता है.
  • Android 12 से पहले के वर्शन वाले डिवाइसों में, साइज़ का हिसाब लगाने का लॉजिक हर स्थिति में काम नहीं कर सकता.

आम तौर पर, इस मोड का इस्तेमाल तब करना चाहिए, जब SizeMode.Responsive का इस्तेमाल न किया जा सके. इसका मतलब है कि रिस्पॉन्सिव लेआउट का छोटा सेट इस्तेमाल नहीं किया जा सकता.

संसाधन ऐक्सेस करना

किसी भी Android रिसॉर्स को ऐक्सेस करने के लिए, LocalContext.current का इस्तेमाल करें. जैसा कि यहां दिए गए उदाहरण में दिखाया गया है:

LocalContext.current.getString(R.string.glance_title)

हमारा सुझाव है कि सीधे तौर पर संसाधन आईडी दें, ताकि फ़ाइनल RemoteViews ऑब्जेक्ट का साइज़ कम किया जा सके. साथ ही, डाइनैमिक कलर जैसे डाइनैमिक संसाधनों को चालू किया जा सके.

कंपोज़ेबल और तरीके, "प्रोवाइडर" का इस्तेमाल करके संसाधनों को स्वीकार करते हैं. जैसे, ImageProvider या ओवरलोड तरीके का इस्तेमाल करके, जैसे कि GlanceModifier.background(R.color.blue). उदाहरण के लिए:

Column(
    modifier = GlanceModifier.background(R.color.default_widget_background)
) { /**...*/ }

Image(
    provider = ImageProvider(R.drawable.ic_logo),
    contentDescription = "My image",
)

टेक्स्ट को मैनेज करना

Glance 1.1.0 में, टेक्स्ट स्टाइल सेट करने के लिए एक एपीआई शामिल है. TextStyle क्लास के fontSize, fontWeight या fontFamily एट्रिब्यूट का इस्तेमाल करके, टेक्स्ट की स्टाइल सेट करें.

fontFamily सभी सिस्टम फ़ॉन्ट के साथ काम करता है. जैसा कि इस उदाहरण में दिखाया गया है. हालांकि, ऐप्लिकेशन में कस्टम फ़ॉन्ट इस्तेमाल नहीं किए जा सकते:

Text(
    style = TextStyle(
        fontWeight = FontWeight.Bold,
        fontSize = 18.sp,
        fontFamily = FontFamily.Monospace
    ),
    text = "Example Text"
)

कंपाउंड बटन जोड़ना

कंपाउंड बटन, Android 12 में पेश किए गए थे. Glance, इस तरह के कंपाउंड बटन के साथ काम करता है:

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

var isApplesChecked by remember { mutableStateOf(false) }
var isEnabledSwitched by remember { mutableStateOf(false) }
var isRadioChecked by remember { mutableIntStateOf(0) }

CheckBox(
    checked = isApplesChecked,
    onCheckedChange = { isApplesChecked = !isApplesChecked },
    text = "Apples"
)

Switch(
    checked = isEnabledSwitched,
    onCheckedChange = { isEnabledSwitched = !isEnabledSwitched },
    text = "Enabled"
)

RadioButton(
    checked = isRadioChecked == 1,
    onClick = { isRadioChecked = 1 },
    text = "Checked"
)

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

class MyAppWidget : GlanceAppWidget() {

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        val myRepository = MyRepository.getInstance()

        provideContent {
            val scope = rememberCoroutineScope()

            val saveApple: (Boolean) -> Unit =
                { scope.launch { myRepository.saveApple(it) } }
            MyContent(saveApple)
        }
    }

    @Composable
    private fun MyContent(saveApple: (Boolean) -> Unit) {

        var isAppleChecked by remember { mutableStateOf(false) }

        Button(
            text = "Save",
            onClick = { saveApple(isAppleChecked) }
        )
    }
}

CheckBox, Switch, और RadioButton के रंग को पसंद के मुताबिक बनाने के लिए, colors एट्रिब्यूट की वैल्यू भी दी जा सकती है:

CheckBox(
    // ...
    colors = CheckboxDefaults.colors(
        checkedColor = ColorProvider(day = colorAccentDay, night = colorAccentNight),
        uncheckedColor = ColorProvider(day = Color.DarkGray, night = Color.LightGray)
    ),
    checked = isChecked,
    onCheckedChange = { isChecked = !isChecked }
)

Switch(
    // ...
    colors = SwitchDefaults.colors(
        checkedThumbColor = ColorProvider(day = Color.Red, night = Color.Cyan),
        uncheckedThumbColor = ColorProvider(day = Color.Green, night = Color.Magenta),
        checkedTrackColor = ColorProvider(day = Color.Blue, night = Color.Yellow),
        uncheckedTrackColor = ColorProvider(day = Color.Magenta, night = Color.Green)
    ),
    checked = isChecked,
    onCheckedChange = { isChecked = !isChecked },
    text = "Enabled"
)

RadioButton(
    // ...
    colors = RadioButtonDefaults.colors(
        checkedColor = ColorProvider(day = Color.Cyan, night = Color.Yellow),
        uncheckedColor = ColorProvider(day = Color.Red, night = Color.Blue)
    ),

)

अन्य कॉम्पोनेंट

Glance 1.1.0 में, अतिरिक्त कॉम्पोनेंट रिलीज़ किए गए हैं. इनके बारे में यहां दी गई टेबल में बताया गया है:

नाम इमेज रेफ़रंस लिंक अतिरिक्त जानकारी
फ़िल्ड बटन alt_text कॉम्पोनेंट
आउटलाइन वाले बटन alt_text कॉम्पोनेंट
आइकॉन बटन alt_text कॉम्पोनेंट प्राइमरी / सेकंडरी / सिर्फ़ आइकॉन
टाइटल बार alt_text कॉम्पोनेंट
स्काफ़ोल्ड स्काफ़ोल्ड और टाइटल बार, दोनों एक ही डेमो में मौजूद हैं.

डिज़ाइन की खास बातों के बारे में ज़्यादा जानने के लिए, Figma पर मौजूद इस डिज़ाइन किट में कॉम्पोनेंट के डिज़ाइन देखें.

कैननिकल लेआउट के बारे में ज़्यादा जानने के लिए, कैननिकल विजेट लेआउट पर जाएं.