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

পরিবর্তে Remote Compose ব্যবহার করা হয়।

স্ন্যাপ স্ক্রলিং

স্ন্যাপ স্ক্রলিং হল একটি অ্যানিমেশন যা স্ক্রল করা যায় এমন কন্টেন্টকে উইজেট কন্টেনারের সবচেয়ে উপরে স্ন্যাপ করতে দেয়।

ভিডিও ১. বাঁদিকে এমন একটি তালিকা আইটেম দেখানো হয়েছে যা স্ক্রল করার সময় সঠিক জায়গায় স্ন্যাপ করে না, ডানদিকেরটি সঠিক জায়গায় স্ন্যাপ করে।


স্ন্যাপ স্ক্রলিং প্রয়োগ করতে, নিম্নলিখিত শর্ত পূরণ করেছেন কিনা তা ভালোভাবে দেখে নিন:

  • আপনার 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
        // ...
    }
}

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

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

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

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

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

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