Glance-এর সাহায্যে UI তৈরি করা

এই পৃষ্ঠায়, আগে থেকে থাকা Glance কম্পোনেন্ট ব্যবহার করে, কীভাবে সাইজ ম্যানেজ করতে হয় এবং Glance-এর সাথে ফ্লেক্সিবল ও রেসপন্সিভ লেআউট প্রদান করতে হয় তা বর্ণনা করা হয়েছে।

Box, Column ও Row ব্যবহার করুন

Glance-এ তিনটি প্রধান কম্পোজেবল লেআউট আছে:

  • Box: একটির উপরে আরেকটি এলিমেন্ট রাখে। এটি RelativeLayout-এ অনুবাদ করা হয়।

  • Column: উল্লম্ব অক্ষে একটির পর একটি এলিমেন্ট রাখে। এটি উল্লম্ব ওরিয়েন্টেশন সহ LinearLayout-এ অনুবাদ করে।

  • Row: অনুভূমিক অক্ষে একটির পর একটি এলিমেন্ট রাখে। এটি অনুভূমিক ওরিয়েন্টেশন সহ LinearLayout-এ অনুবাদ করে।

Glance-এ Scaffold অবজেক্ট কাজ করে। প্রদত্ত Scaffold অবজেক্টের মধ্যে আপনার Column, Row এবং Box কম্পোজেবল রাখুন।

কলাম, সারি ও বক্স লেআউট।
ছবি ১. কলাম, সারি ও বক্স সহ লেআউটের উদাহরণ।

এইসব কম্পোজেবলের প্রত্যেকটি আপনাকে মডিফায়ার ব্যবহার করে কন্টেন্টের উল্লম্ব ও অনুভূমিক অ্যালাইনমেন্ট এবং প্রস্থ, উচ্চতা, ওজন বা প্যাডিং সংক্রান্ত সীমাবদ্ধতা নির্ধারণ করতে দেয়। এছাড়াও, প্রতিটি চাইল্ড, পেরেন্টের মধ্যে স্পেস ও প্লেসমেন্ট পরিবর্তন করার জন্য তার মডিফায়ার নির্ধারণ করতে পারে।

নিম্নলিখিত উদাহরণ থেকে আপনি জানতে পারবেন কীভাবে এমন একটি 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-এর ভার্সন ৩৭ বা তার বেশি সেট করুন, কারণ স্ন্যাপ স্ক্রলিং Android ১৭ বা তার বেশি ভার্সনে চলা ডিভাইসে কাজ করে।
  • 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
        // ...
    }
}

এই মোড ব্যবহার করার সময়, এগুলি নিশ্চিত করুন:

  • কন্টেন্টের সাইজের উপর ভিত্তি করে সর্বনিম্ন ও সর্বোচ্চ সাইজের মেটাডেটা ভ্যালু সঠিকভাবে নির্ধারণ করা হয়েছে।
  • প্রত্যাশিত সাইজের রেঞ্জের মধ্যে কন্টেন্টটি যথেষ্ট ফ্লেক্সিবল।

সাধারণত, নিম্নলিখিত যেকোনও একটি ক্ষেত্রে আপনার এই মোড ব্যবহার করা উচিত:

ক) AppWidget-এর সাইজ নির্দিষ্ট, অথবা খ) ছোট বড় করা হলে এটি কন্টেন্ট পরিবর্তন করে না।

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 উপলভ্য সাইজের উপর নির্ভর করে সাইজের মান দেখানো হয়েছে:

উপলভ্য সাইজ ১০৫ x ১১০ ২০৩ x ১১২ ৭২ x ৭২ ২০৩ x ১৫০
SizeMode.Single ১১০ x ১১০ ১১০ x ১১০ ১১০ x ১১০ ১১০ x ১১০
SizeMode.Exact ১০৫ x ১১০ ২০৩ x ১১২ ৭২ x ৭২ ২০৩ x ১৫০
SizeMode.Responsive ৮০ x ১০০ ৮০ x ১০০ ৮০ x ১০০ ১৫০ x ১২০
* সঠিক ভ্যালু শুধুমাত্র ডেমোর উদ্দেশ্যে দেখানো হয়েছে।

SizeMode.Exact

SizeMode.Exact হল সঠিক লেআউট প্রদান করার সমতুল্য, যা প্রতিবার উপলভ্য AppWidget সাইজ পরিবর্তন হলে (যেমন, ব্যবহারকারী হোম স্ক্রিনে AppWidget-এর সাইজ পরিবর্তন করলে) GlanceAppWidget কন্টেন্টের জন্য অনুরোধ করে।

যেমন, গন্তব্য উইজেটে, উপলভ্য প্রস্থ নির্দিষ্ট মানের থেকে বেশি হলে, একটি অতিরিক্ত বোতাম যোগ করা যেতে পারে।

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 সম্পূর্ণভাবে আবার তৈরি করতে হবে। কন্টেন্ট জটিল হলে, এর ফলে পারফর্ম্যান্স সংক্রান্ত সমস্যা এবং UI জাম্প হতে পারে।
  • লঞ্চারের প্রয়োগের উপর নির্ভর করে উপলভ্য সাইজ আলাদা হতে পারে। যেমন, লঞ্চার যদি সাইজের তালিকা না দেয়, তাহলে সম্ভাব্য ন্যূনতম সাইজ ব্যবহার করা হয়।
  • 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-এ আপনার টেক্সট স্টাইল সেট করার জন্য একটি API অন্তর্ভুক্ত আছে। 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) }
        )
    }
}

এছাড়াও, আপনি colors অ্যাট্রিবিউট CheckBox, Switch এবং RadioButton-এ প্রদান করতে পারেন যাতে সেগুলির রঙ কাস্টমাইজ করা যায়:

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-তে এই ডিজাইন কিট-এ কম্পোনেন্ট ডিজাইন দেখুন।

ক্যানোনিকাল লেআউট সম্পর্কে আরও জানতে ক্যানোনিকাল উইজেট লেআউট দেখুন।