এক নজরে UI তৈরি করুন

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

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

গ্ল্যান্সের তিনটি প্রধান কম্পোজেবল লেআউট রয়েছে:

  • 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 উল্লেখ করলে অ্যান্ড্রয়েড ১২ থেকে শুরু করে লিস্ট এবং appWidget আপডেটের মাধ্যমে পারফরম্যান্স উন্নত করতে এবং স্ক্রল পজিশন বজায় রাখতে সাহায্য করে (উদাহরণস্বরূপ, লিস্ট থেকে আইটেম যোগ বা অপসারণ করার সময়)। নিচের উদাহরণটিতে দেখানো হয়েছে কীভাবে একটি itemId উল্লেখ করতে হয়:

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

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

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

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


স্ন্যাপ স্ক্রলিং কার্যকর করতে, নিম্নলিখিত শর্তগুলো পূরণ করা নিশ্চিত করুন:

  • আপনার Glance ডিপেন্ডেন্সি 1.3.0-alpha02 বা উচ্চতর সংস্করণে আপডেট করুন।
  • আপনার compileSdk ৩৭ বা তার বেশিতে সেট করুন, কারণ স্ন্যাপ স্ক্রলিং অ্যান্ড্রয়েড ১৭ এবং তার পরবর্তী সংস্করণ চালিত ডিভাইসগুলিতে সমর্থিত।
  • আপনার LazyColumn VerticalScrollMode দিয়ে কনফিগার করুন। ডিভাইসটি স্ন্যাপ স্ক্রলিং সমর্থন করলে, 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 আকার ভিন্ন হতে পারে, তাই 'Provide flexible widget layouts' পৃষ্ঠায় বর্ণিত পদ্ধতি অনুযায়ী নমনীয় লেআউট প্রদান করা গুরুত্বপূর্ণ। 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 ঝাঁকুনির মতো ঘটনা ঘটতে পারে।
  • লঞ্চারের বাস্তবায়নের উপর নির্ভর করে উপলব্ধ আকার ভিন্ন হতে পারে। উদাহরণস্বরূপ, যদি লঞ্চারটি আকারের তালিকা প্রদান না করে, তবে সর্বনিম্ন সম্ভাব্য আকারটি ব্যবহৃত হয়।
  • অ্যান্ড্রয়েড ১২-এর পূর্ববর্তী ডিভাইসগুলোতে সাইজ গণনার কার্যপ্রণালী সব পরিস্থিতিতে কাজ নাও করতে পারে।

সাধারণত, আপনার এই মোডটি ব্যবহার করা উচিত যদি SizeMode.Responsive ব্যবহার করা সম্ভব না হয় (অর্থাৎ, অল্প কিছু রেসপন্সিভ লেআউট তৈরি করা সম্ভব না হয়)।

সম্পদ অ্যাক্সেস করুন

যেকোনো অ্যান্ড্রয়েড রিসোর্স অ্যাক্সেস করতে 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"
)

যৌগিক বোতাম যোগ করুন

কম্পাউন্ড বাটন অ্যান্ড্রয়েড ১২-এ চালু করা হয়েছিল। গ্ল্যান্স নিম্নলিখিত ধরনের কম্পাউন্ড বাটনগুলোর জন্য ব্যাকওয়ার্ড কম্প্যাটিবিলিটি সমর্থন করে:

এই যৌগিক বাটনগুলোর প্রতিটিতে একটি ক্লিকযোগ্য দৃশ্য প্রদর্শিত হয়, যা 'চেক করা' অবস্থাটিকে বোঝায়।

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

)

অতিরিক্ত উপাদান

গ্লান্স ১.১.০ সংস্করণে অতিরিক্ত কিছু উপাদান যুক্ত করা হয়েছে, যা নিম্নলিখিত সারণিতে বর্ণনা করা হলো:

নাম ছবি রেফারেন্স লিঙ্ক অতিরিক্ত নোট
ভরা বোতাম alt_text উপাদান
রূপরেখা বোতাম alt_text উপাদান
আইকন বোতাম alt_text উপাদান প্রাথমিক / মাধ্যমিক / শুধুমাত্র আইকন
শিরোনাম বার alt_text উপাদান
স্ক্যাফোল্ড স্ক্যাফোল্ড এবং টাইটেল বার একই ডেমোতে রয়েছে।

ডিজাইনের নির্দিষ্ট বিবরণ সম্পর্কে আরও তথ্যের জন্য, ফিগমাতে এই ডিজাইন কিটে থাকা কম্পোনেন্ট ডিজাইনগুলো দেখুন।

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