তালিকা এবং গ্রিড

অনেক অ্যাপের আইটেমের সংগ্রহ প্রদর্শনের প্রয়োজন হয়। এই ডকুমেন্টটি ব্যাখ্যা করে যে কীভাবে আপনি জেটপ্যাক কম্পোজে দক্ষতার সাথে এটি করতে পারেন।

যদি আপনি জানেন যে আপনার ব্যবহারের ক্ষেত্রে কোনও স্ক্রলিং প্রয়োজন হয় না, তাহলে আপনি একটি সাধারণ Column বা Row ব্যবহার করতে পারেন (দিকের উপর নির্ভর করে), এবং নিম্নলিখিত উপায়ে একটি তালিকার উপর পুনরাবৃত্তি করে প্রতিটি আইটেমের বিষয়বস্তু নির্গত করতে পারেন:

@Composable
fun MessageList(messages: List<Message>) {
    Column {
        messages.forEach { message ->
            MessageRow(message)
        }
    }
}

আমরা verticalScroll() মডিফায়ার ব্যবহার করে Column স্ক্রোলযোগ্য করতে পারি।

অলস তালিকা

যদি আপনার প্রচুর সংখ্যক আইটেম (অথবা অজানা দৈর্ঘ্যের একটি তালিকা) প্রদর্শনের প্রয়োজন হয়, তাহলে Column মতো লেআউট ব্যবহার করলে কর্মক্ষমতা সংক্রান্ত সমস্যা হতে পারে, কারণ সমস্ত আইটেম দৃশ্যমান হোক বা না হোক, সেগুলো তৈরি এবং সাজানো হবে।

কম্পোজ এমন কিছু উপাদানের সেট প্রদান করে যা শুধুমাত্র উপাদানের ভিউপোর্টে দৃশ্যমান আইটেমগুলি রচনা এবং লেআউট করে। এই উপাদানগুলির মধ্যে রয়েছে LazyColumn এবং LazyRow

নাম থেকেই বোঝা যায়, LazyColumn এবং LazyRow মধ্যে পার্থক্য হলো তারা তাদের আইটেমগুলি কোন দিকে ছেড়ে রাখে এবং স্ক্রোল করে। LazyColumn একটি উল্লম্বভাবে স্ক্রলিং তালিকা তৈরি করে, এবং LazyRow একটি অনুভূমিকভাবে স্ক্রলিং তালিকা তৈরি করে।

Lazy কম্পোনেন্টগুলি Compose-এর বেশিরভাগ লেআউট থেকে আলাদা। @Composable কন্টেন্ট ব্লক প্যারামিটার গ্রহণ করার পরিবর্তে, অ্যাপগুলিকে সরাসরি কম্পোজেবল নির্গত করার অনুমতি দেয়, Lazy কম্পোনেন্টগুলি একটি LazyListScope.() ব্লক প্রদান করে। এই LazyListScope ব্লকটি একটি DSL অফার করে যা অ্যাপগুলিকে আইটেমের বিষয়বস্তু বর্ণনা করতে দেয়। এরপর Lazy কম্পোনেন্টটি লেআউট এবং স্ক্রোল অবস্থান অনুসারে প্রতিটি আইটেমের বিষয়বস্তু যোগ করার জন্য দায়ী।

LazyListScope DSL সম্পর্কে

LazyListScope এর DSL লেআউটে আইটেম বর্ণনা করার জন্য বেশ কয়েকটি ফাংশন প্রদান করে। সবচেয়ে সাধারণভাবে, item() একটি একক আইটেম যোগ করে এবং items(Int) একাধিক আইটেম যোগ করে:

LazyColumn {
    // Add a single item
    item {
        Text(text = "First item")
    }

    // Add 5 items
    items(5) { index ->
        Text(text = "Item: $index")
    }

    // Add another single item
    item {
        Text(text = "Last item")
    }
}

এছাড়াও বেশ কিছু এক্সটেনশন ফাংশন রয়েছে যা আপনাকে আইটেমের সংগ্রহ যোগ করতে দেয়, যেমন একটি List । এই এক্সটেনশনগুলি আমাদের উপর থেকে আমাদের Column উদাহরণ সহজেই স্থানান্তর করতে দেয়:

/**
 * import androidx.compose.foundation.lazy.items
 */
LazyColumn {
    items(messages) { message ->
        MessageRow(message)
    }
}

items() এক্সটেনশন ফাংশনের একটি রূপও আছে যার নাম itemsIndexed() , যা সূচক প্রদান করে। আরও বিস্তারিত জানার জন্য অনুগ্রহ করে LazyListScope রেফারেন্স দেখুন।

অলস গ্রিড

LazyVerticalGrid এবং LazyHorizontalGrid কম্পোজেবলগুলি একটি গ্রিডে আইটেম প্রদর্শনের জন্য সহায়তা প্রদান করে। একটি Lazy উল্লম্ব গ্রিড তার আইটেমগুলিকে একটি উল্লম্বভাবে স্ক্রোলযোগ্য কন্টেইনারে প্রদর্শন করবে, যা একাধিক কলামে বিস্তৃত থাকবে, যখন Lazy অনুভূমিক গ্রিডগুলি অনুভূমিক অক্ষে একই আচরণ করবে।

গ্রিডগুলিতে তালিকার মতোই শক্তিশালী API ক্ষমতা রয়েছে এবং তারা বিষয়বস্তু বর্ণনা করার জন্য খুব অনুরূপ DSL - LazyGridScope.() ব্যবহার করে।

ফোনের স্ক্রিনশট যেখানে ছবির গ্রিড দেখানো হচ্ছে

LazyVerticalGridcolumns প্যারামিটার এবং LazyHorizontalGridrows প্যারামিটার নিয়ন্ত্রণ করে কিভাবে কোষগুলি কলাম বা সারিতে গঠিত হয়। নিম্নলিখিত উদাহরণটি একটি গ্রিডে আইটেমগুলি প্রদর্শন করে, প্রতিটি কলামকে কমপক্ষে 128.dp প্রস্থে সেট করতে GridCells.Adaptive ব্যবহার করে:

LazyVerticalGrid(
    columns = GridCells.Adaptive(minSize = 128.dp)
) {
    items(photos) { photo ->
        PhotoItem(photo)
    }
}

LazyVerticalGrid আপনাকে আইটেমগুলির জন্য একটি প্রস্থ নির্দিষ্ট করতে দেয়, এবং তারপর গ্রিডটি যতটা সম্ভব কলাম ফিট করবে। কলামের সংখ্যা গণনা করার পরে, অবশিষ্ট প্রস্থ কলামের মধ্যে সমানভাবে বিতরণ করা হয়। আকার পরিবর্তনের এই অভিযোজিত উপায়টি বিভিন্ন স্ক্রিন আকারে আইটেমগুলির সেট প্রদর্শনের জন্য বিশেষভাবে কার্যকর।

যদি আপনি সঠিক কলাম সংখ্যা জানেন, তাহলে আপনি GridCells.Fixed এর একটি উদাহরণ প্রদান করতে পারেন যেখানে প্রয়োজনীয় কলাম সংখ্যা থাকবে।

যদি আপনার ডিজাইনে শুধুমাত্র কিছু নির্দিষ্ট আইটেমের জন্য অ-মানক মাত্রা প্রয়োজন হয়, তাহলে আপনি আইটেমগুলির জন্য কাস্টম কলাম স্প্যান প্রদানের জন্য গ্রিড সাপোর্ট ব্যবহার করতে পারেন। LazyGridScope DSL item এবং items পদ্ধতির span প্যারামিটার দিয়ে কলাম স্প্যান নির্দিষ্ট করুন। maxLineSpan , স্প্যান স্কোপের মানগুলির মধ্যে একটি, বিশেষ করে যখন আপনি অভিযোজিত আকার পরিবর্তন ব্যবহার করেন তখন কার্যকর, কারণ কলামের সংখ্যা স্থির থাকে না। এই উদাহরণটি দেখায় কিভাবে একটি সম্পূর্ণ সারি স্প্যান প্রদান করতে হয়:

LazyVerticalGrid(
    columns = GridCells.Adaptive(minSize = 30.dp)
) {
    item(span = {
        // LazyGridItemSpanScope:
        // maxLineSpan
        GridItemSpan(maxLineSpan)
    }) {
        CategoryCard("Fruits")
    }
    // ...
}

অলস স্তব্ধ গ্রিড

LazyVerticalStaggeredGrid এবং LazyHorizontalStaggeredGrid হল কম্পোজেবল যা আপনাকে আইটেমগুলির একটি অলস-লোডেড, স্ট্যাগার্ড গ্রিড তৈরি করতে দেয়। একটি অলস উল্লম্ব স্ট্যাগার্ড গ্রিড তার আইটেমগুলিকে একটি উল্লম্বভাবে স্ক্রোলযোগ্য কন্টেইনারে প্রদর্শন করে যা একাধিক কলামে বিস্তৃত এবং পৃথক আইটেমগুলিকে বিভিন্ন উচ্চতায় থাকতে দেয়। বিভিন্ন প্রস্থের আইটেমগুলির সাথে অনুভূমিক অক্ষে Lazy অনুভূমিক গ্রিডগুলির একই আচরণ থাকে।

নিম্নলিখিত স্নিপেটটি LazyVerticalStaggeredGrid ব্যবহারের একটি মৌলিক উদাহরণ যার প্রতিটি আইটেমের প্রস্থ 200.dp :

LazyVerticalStaggeredGrid(
    columns = StaggeredGridCells.Adaptive(200.dp),
    verticalItemSpacing = 4.dp,
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    content = {
        items(randomSizedPhotos) { photo ->
            AsyncImage(
                model = photo,
                contentScale = ContentScale.Crop,
                contentDescription = null,
                modifier = Modifier
                    .fillMaxWidth()
                    .wrapContentHeight()
            )
        }
    },
    modifier = Modifier.fillMaxSize()
)

চিত্র ১. অলস স্তব্ধ উল্লম্ব গ্রিডের উদাহরণ

নির্দিষ্ট সংখ্যক কলামের সংখ্যা নির্ধারণ করতে, আপনি StaggeredGridCells.Adaptive এর পরিবর্তে StaggeredGridCells.Fixed(columns) ব্যবহার করতে পারেন। এটি উপলব্ধ প্রস্থকে কলামের সংখ্যা (অথবা অনুভূমিক গ্রিডের জন্য সারি) দিয়ে ভাগ করে, এবং প্রতিটি আইটেম সেই প্রস্থ (অথবা অনুভূমিক গ্রিডের জন্য উচ্চতা) গ্রহণ করে:

LazyVerticalStaggeredGrid(
    columns = StaggeredGridCells.Fixed(3),
    verticalItemSpacing = 4.dp,
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    content = {
        items(randomSizedPhotos) { photo ->
            AsyncImage(
                model = photo,
                contentScale = ContentScale.Crop,
                contentDescription = null,
                modifier = Modifier
                    .fillMaxWidth()
                    .wrapContentHeight()
            )
        }
    },
    modifier = Modifier.fillMaxSize()
)
Lazy staggered grid of images in Compose
চিত্র ২। স্থির কলাম সহ অলস স্তম্ভিত উল্লম্ব গ্রিডের উদাহরণ

কন্টেন্ট প্যাডিং

কখনও কখনও আপনাকে কন্টেন্টের প্রান্তের চারপাশে প্যাডিং যোগ করতে হবে। লেজি কম্পোনেন্টগুলি আপনাকে এটি সমর্থন করার জন্য contentPadding প্যারামিটারে কিছু PaddingValues ​​পাস করার অনুমতি দেয়:

LazyColumn(
    contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
) {
    // ...
}

এই উদাহরণে, আমরা অনুভূমিক প্রান্তগুলিতে (বাম এবং ডান) 16.dp প্যাডিং যোগ করব, এবং তারপর কন্টেন্টের উপরে এবং নীচে 8.dp যোগ করব।

দয়া করে মনে রাখবেন যে এই প্যাডিংটি কন্টেন্টে প্রয়োগ করা হয়েছে, LazyColumn এ নয়। উপরের উদাহরণে, প্রথম আইটেমটি তার উপরে 8.dp প্যাডিং যোগ করবে, শেষ আইটেমটি তার নীচে 8.dp যোগ করবে এবং সমস্ত আইটেমের বাম এবং ডানে 16.dp প্যাডিং থাকবে।

আরেকটি উদাহরণ হিসেবে, আপনি Scaffold এর PaddingValues LazyColumn এর contentPadding এ পাস করতে পারেন। edge-to-edge গাইডটি দেখুন।

কন্টেন্টের ব্যবধান

আইটেমগুলির মধ্যে ব্যবধান যোগ করতে, আপনি Arrangement.spacedBy() ব্যবহার করতে পারেন। নীচের উদাহরণটি প্রতিটি আইটেমের মধ্যে 4.dp স্থান যোগ করে:

LazyColumn(
    verticalArrangement = Arrangement.spacedBy(4.dp),
) {
    // ...
}

একইভাবে LazyRow এর জন্য:

LazyRow(
    horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
    // ...
}

তবে, গ্রিডগুলি উল্লম্ব এবং অনুভূমিক উভয় বিন্যাস গ্রহণ করে:

LazyVerticalGrid(
    columns = GridCells.Fixed(2),
    verticalArrangement = Arrangement.spacedBy(16.dp),
    horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
    items(photos) { item ->
        PhotoItem(item)
    }
}

আইটেম কী

ডিফল্টরূপে, প্রতিটি আইটেমের অবস্থা তালিকা বা গ্রিডে থাকা আইটেমের অবস্থানের বিপরীতে থাকে। তবে, ডেটা সেট পরিবর্তন হলে এটি সমস্যার সৃষ্টি করতে পারে, কারণ অবস্থান পরিবর্তনকারী আইটেমগুলি কার্যকরভাবে কোনও মনে রাখা অবস্থা হারায়। আপনি যদি LazyColumn এর মধ্যে LazyRow এর দৃশ্যকল্প কল্পনা করেন, যদি সারিটি আইটেমের অবস্থান পরিবর্তন করে, তাহলে ব্যবহারকারী সারির মধ্যে তাদের স্ক্রোল অবস্থান হারাবে।

এই সমস্যা মোকাবেলা করার জন্য, আপনি প্রতিটি আইটেমের জন্য একটি স্থিতিশীল এবং অনন্য কী প্রদান করতে পারেন, যা key প্যারামিটারে একটি ব্লক প্রদান করে। একটি স্থিতিশীল কী প্রদান করলে আইটেমের অবস্থা ডেটা-সেট পরিবর্তনের সাথে সামঞ্জস্যপূর্ণ হতে পারে:

LazyColumn {
    items(
        items = messages,
        key = { message ->
            // Return a stable + unique key for the item
            message.id
        }
    ) { message ->
        MessageRow(message)
    }
}

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

LazyColumn {
    items(books, key = { it.id }) {
        val rememberedValue = remember {
            Random.nextInt()
        }
    }
}

তবে, আইটেম কী হিসেবে কোন ধরণের কী ব্যবহার করা যাবে তার একটি সীমাবদ্ধতা রয়েছে। কী-এর ধরণটি অবশ্যই Bundle দ্বারা সমর্থিত হতে হবে, যা অ্যাক্টিভিটি পুনরায় তৈরি করার সময় অবস্থা বজায় রাখার জন্য অ্যান্ড্রয়েডের প্রক্রিয়া। Bundle primitives, enums বা Parcelables এর মতো ধরণের কী-গুলিকে সমর্থন করে।

LazyColumn {
    items(books, key = {
        // primitives, enums, Parcelable, etc.
    }) {
        // ...
    }
}

কীটি অবশ্যই Bundle দ্বারা সমর্থিত হতে হবে যাতে Activity পুনরায় তৈরি করার সময়, অথবা আপনি যখন এই আইটেমটি থেকে সরে এসে পিছনে স্ক্রোল করেন তখনও composable আইটেমের ভিতরে থাকা rememberSaveable পুনরুদ্ধার করা যায়।

LazyColumn {
    items(books, key = { it.id }) {
        val rememberedValue = rememberSaveable {
            Random.nextInt()
        }
    }
}

আইটেম অ্যানিমেশন

যদি আপনি RecyclerView উইজেট ব্যবহার করে থাকেন, তাহলে আপনি জানতে পারবেন যে এটি আইটেম পরিবর্তনগুলিকে স্বয়ংক্রিয়ভাবে অ্যানিমেট করে । অলস লেআউটগুলি আইটেম পুনর্বিন্যাসের জন্য একই কার্যকারিতা প্রদান করে। APIটি সহজ - আপনাকে কেবল আইটেম সামগ্রীতে animateItem মডিফায়ার সেট করতে হবে:

LazyColumn {
    // It is important to provide a key to each item to ensure animateItem() works as expected.
    items(books, key = { it.id }) {
        Row(Modifier.animateItem()) {
            // ...
        }
    }
}

আপনার যদি প্রয়োজন হয় তবে আপনি কাস্টম অ্যানিমেশন স্পেসিফিকেশনও প্রদান করতে পারেন:

LazyColumn {
    items(books, key = { it.id }) {
        Row(
            Modifier.animateItem(
                fadeInSpec = tween(durationMillis = 250),
                fadeOutSpec = tween(durationMillis = 100),
                placementSpec = spring(stiffness = Spring.StiffnessLow, dampingRatio = Spring.DampingRatioMediumBouncy)
            )
        ) {
            // ...
        }
    }
}

আপনার আইটেমগুলির জন্য কীগুলি সরবরাহ করুন যাতে সরানো উপাদানটির জন্য নতুন অবস্থান খুঁজে পাওয়া সম্ভব হয়।

উদাহরণ: অলস তালিকায় আইটেম অ্যানিমেট করুন

কম্পোজের সাহায্যে, আপনি অলস তালিকার আইটেমগুলিতে পরিবর্তনগুলি অ্যানিমেট করতে পারেন। একসাথে ব্যবহার করা হলে, নিম্নলিখিত স্নিপেটগুলি অলস তালিকার আইটেমগুলি যোগ করার, অপসারণ করার এবং পুনরায় সাজানোর সময় অ্যানিমেশন বাস্তবায়ন করে।

আইটেম যোগ করা, সরানো বা পুনঃক্রম করা হলে এই স্নিপেটটি অ্যানিমেটেড ট্রানজিশন সহ স্ট্রিংগুলির একটি তালিকা প্রদর্শন করে:

@Composable
fun ListAnimatedItems(
    items: List<String>,
    modifier: Modifier = Modifier
) {
    LazyColumn(modifier) {
        // Use a unique key per item, so that animations work as expected.
        items(items, key = { it }) {
            ListItem(
                headlineContent = { Text(it) },
                modifier = Modifier
                    .animateItem(
                        // Optionally add custom animation specs
                    )
                    .fillParentMaxWidth()
                    .padding(horizontal = 8.dp, vertical = 0.dp),
            )
        }
    }
}

কোড সম্পর্কে গুরুত্বপূর্ণ বিষয়সমূহ

  • ListAnimatedItems একটি LazyColumn এ স্ট্রিংগুলির একটি তালিকা প্রদর্শন করে যেখানে আইটেমগুলি পরিবর্তন করা হলে অ্যানিমেটেড ট্রানজিশন থাকে।
  • items ফাংশনটি তালিকার প্রতিটি আইটেমের জন্য একটি অনন্য কী বরাদ্দ করে। কম্পোজ আইটেমগুলি ট্র্যাক করতে এবং তাদের অবস্থানের পরিবর্তনগুলি সনাক্ত করতে কীগুলি ব্যবহার করে।
  • ListItem প্রতিটি তালিকার আইটেমের লেআউট নির্ধারণ করে। এটি একটি headlineContent প্যারামিটার নেয়, যা আইটেমের মূল বিষয়বস্তু নির্ধারণ করে।
  • animateItem মডিফায়ার আইটেম সংযোজন, অপসারণ এবং সরানোর ক্ষেত্রে ডিফল্ট অ্যানিমেশন প্রয়োগ করে।

নিম্নলিখিত স্নিপেটটি এমন একটি স্ক্রিন উপস্থাপন করে যা আইটেম যোগ এবং অপসারণের জন্য নিয়ন্ত্রণগুলি অন্তর্ভুক্ত করে, পাশাপাশি একটি পূর্বনির্ধারিত তালিকা সাজানোর জন্যও:

@Composable
private fun ListAnimatedItemsExample(
    data: List<String>,
    modifier: Modifier = Modifier,
    onAddItem: () -> Unit = {},
    onRemoveItem: () -> Unit = {},
    resetOrder: () -> Unit = {},
    onSortAlphabetically: () -> Unit = {},
    onSortByLength: () -> Unit = {},
) {
    val canAddItem = data.size < 10
    val canRemoveItem = data.isNotEmpty()

    Scaffold(modifier) { paddingValues ->
        Column(
            modifier = Modifier
                .padding(paddingValues)
                .fillMaxSize()
        ) {
            // Buttons that change the value of displayedItems.
            AddRemoveButtons(canAddItem, canRemoveItem, onAddItem, onRemoveItem)
            OrderButtons(resetOrder, onSortAlphabetically, onSortByLength)

            // List that displays the values of displayedItems.
            ListAnimatedItems(data)
        }
    }
}

কোড সম্পর্কে গুরুত্বপূর্ণ বিষয়সমূহ

  • ListAnimatedItemsExample একটি স্ক্রিন উপস্থাপন করে যা আইটেম যোগ, অপসারণ এবং সাজানোর জন্য নিয়ন্ত্রণ অন্তর্ভুক্ত করে।
    • onAddItem এবং onRemoveItem হল ল্যাম্বডা এক্সপ্রেশন যা তালিকা থেকে আইটেম যোগ এবং অপসারণের জন্য AddRemoveButtons এ পাঠানো হয়।
    • resetOrder , onSortAlphabetically , এবং onSortByLength হল ল্যাম্বডা এক্সপ্রেশন যা তালিকার আইটেমগুলির ক্রম পরিবর্তন করার জন্য OrderButtons এ পাঠানো হয়।
  • AddRemoveButtons "Add" এবং "Remove" বোতামগুলি প্রদর্শন করে। এটি বোতামগুলি সক্ষম/অক্ষম করে এবং বোতাম ক্লিকগুলি পরিচালনা করে।
  • OrderButtons তালিকা পুনঃক্রম করার জন্য বোতামগুলি প্রদর্শন করে। এটি ক্রম পুনরায় সেট করার এবং তালিকাটিকে দৈর্ঘ্য বা বর্ণানুক্রমিকভাবে সাজানোর জন্য ল্যাম্বডা ফাংশনগুলি গ্রহণ করে।
  • ListAnimatedItems ListAnimatedItems কম্পোজেবল বলে, স্ট্রিংগুলির অ্যানিমেটেড তালিকা প্রদর্শনের জন্য data তালিকাটি পাস করে। data অন্য কোথাও সংজ্ঞায়িত করা হয়।

এই স্নিপেটটি Add Item এবং Delete Item বোতামগুলির সাহায্যে একটি UI তৈরি করে:

@Composable
private fun AddRemoveButtons(
    canAddItem: Boolean,
    canRemoveItem: Boolean,
    onAddItem: () -> Unit,
    onRemoveItem: () -> Unit
) {
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.Center
    ) {
        Button(enabled = canAddItem, onClick = onAddItem) {
            Text("Add Item")
        }
        Spacer(modifier = Modifier.padding(25.dp))
        Button(enabled = canRemoveItem, onClick = onRemoveItem) {
            Text("Delete Item")
        }
    }
}

কোড সম্পর্কে গুরুত্বপূর্ণ বিষয়সমূহ

  • AddRemoveButtons তালিকায় যোগ এবং অপসারণের ক্রিয়াকলাপ সম্পাদনের জন্য বোতামগুলির একটি সারি প্রদর্শন করে।
  • canAddItem এবং canRemoveItem প্যারামিটারগুলি বোতামগুলির সক্রিয় অবস্থা নিয়ন্ত্রণ করে। যদি canAddItem বা canRemoveItem মিথ্যা হয়, তাহলে সংশ্লিষ্ট বোতামটি নিষ্ক্রিয় করা হয়।
  • onAddItem এবং onRemoveItem প্যারামিটারগুলি হল ল্যাম্বডা যা ব্যবহারকারী যখন সংশ্লিষ্ট বোতামে ক্লিক করে তখন কার্যকর হয়।

অবশেষে, এই স্নিপেটটি তালিকাটি সাজানোর জন্য তিনটি বোতাম প্রদর্শন করে ( রিসেট, বর্ণানুক্রমিক এবং দৈর্ঘ্য ):

@Composable
private fun OrderButtons(
    resetOrder: () -> Unit,
    orderAlphabetically: () -> Unit,
    orderByLength: () -> Unit
) {
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.Center
    ) {
        var selectedIndex by remember { mutableIntStateOf(0) }
        val options = listOf("Reset", "Alphabetical", "Length")

        SingleChoiceSegmentedButtonRow {
            options.forEachIndexed { index, label ->
                SegmentedButton(
                    shape = SegmentedButtonDefaults.itemShape(
                        index = index,
                        count = options.size
                    ),
                    onClick = {
                        Log.d("AnimatedOrderedList", "selectedIndex: $selectedIndex")
                        selectedIndex = index
                        when (options[selectedIndex]) {
                            "Reset" -> resetOrder()
                            "Alphabetical" -> orderAlphabetically()
                            "Length" -> orderByLength()
                        }
                    },
                    selected = index == selectedIndex
                ) {
                    Text(label)
                }
            }
        }
    }
}

কোড সম্পর্কে গুরুত্বপূর্ণ বিষয়সমূহ

  • OrderButtons একটি SingleChoiceSegmentedButtonRow প্রদর্শন করে যা ব্যবহারকারীদের তালিকা থেকে একটি সাজানোর পদ্ধতি নির্বাচন করতে বা তালিকার ক্রম পুনরায় সেট করতে দেয়। একটি SegmentedButton উপাদান আপনাকে বিকল্পগুলির তালিকা থেকে একটি একক বিকল্প নির্বাচন করতে দেয়।
  • resetOrder , orderAlphabetically , এবং orderByLength হল ল্যাম্বডা ফাংশন যা সংশ্লিষ্ট বোতামটি নির্বাচন করা হলে কার্যকর করা হয়।
  • selectedIndex state ভেরিয়েবল নির্বাচিত বিকল্পের উপর নজর রাখে।

ফলাফল

এই ভিডিওটিতে আইটেমগুলি পুনঃক্রম করার সময় পূর্ববর্তী স্নিপেটগুলির ফলাফল দেখানো হয়েছে:

চিত্র ১। একটি তালিকা যা আইটেম যোগ করা, সরানো বা সাজানোর সময় আইটেম ট্রানজিশনকে অ্যানিমেট করে।

স্টিকি হেডার (পরীক্ষামূলক)

'স্টিকি হেডার' প্যাটার্নটি গ্রুপ করা ডেটার তালিকা প্রদর্শনের সময় সহায়ক। নীচে আপনি প্রতিটি পরিচিতির আদ্যক্ষর অনুসারে গ্রুপ করা 'পরিচিতি তালিকা'র একটি উদাহরণ দেখতে পাবেন:

একটি ফোনের পরিচিতি তালিকার মধ্য দিয়ে উপরে এবং নীচে স্ক্রোল করার ভিডিও

LazyColumn ব্যবহার করে একটি স্টিকি হেডার অর্জন করতে, আপনি পরীক্ষামূলক stickyHeader() ফাংশন ব্যবহার করতে পারেন, যা হেডারের বিষয়বস্তু প্রদান করে:

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ListWithHeader(items: List<Item>) {
    LazyColumn {
        stickyHeader {
            Header()
        }

        items(items) { item ->
            ItemRow(item)
        }
    }
}

একাধিক হেডার সহ একটি তালিকা অর্জন করতে, যেমন উপরের 'পরিচিতি তালিকা' উদাহরণ, আপনি করতে পারেন:

// This ideally would be done in the ViewModel
val grouped = contacts.groupBy { it.firstName[0] }

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ContactsList(grouped: Map<Char, List<Contact>>) {
    LazyColumn {
        grouped.forEach { (initial, contactsForInitial) ->
            stickyHeader {
                CharacterHeader(initial)
            }

            items(contactsForInitial) { contact ->
                ContactListItem(contact)
            }
        }
    }
}

স্ক্রোল পজিশনে প্রতিক্রিয়া দেখানো

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

@Composable
fun MessageList(messages: List<Message>) {
    // Remember our own LazyListState
    val listState = rememberLazyListState()

    // Provide it to LazyColumn
    LazyColumn(state = listState) {
        // ...
    }
}

সহজ ব্যবহারের ক্ষেত্রে, অ্যাপগুলিকে সাধারণত শুধুমাত্র প্রথম দৃশ্যমান আইটেম সম্পর্কে তথ্য জানতে হয়। এর জন্য LazyListState firstVisibleItemIndex এবং firstVisibleItemScrollOffset বৈশিষ্ট্য প্রদান করে।

ব্যবহারকারী যদি প্রথম আইটেমটি স্ক্রোল করে ফেলেছেন কিনা তার উপর ভিত্তি করে যদি আমরা একটি বোতাম দেখানো এবং লুকানোর উদাহরণ ব্যবহার করি:

@Composable
fun MessageList(messages: List<Message>) {
    Box {
        val listState = rememberLazyListState()

        LazyColumn(state = listState) {
            // ...
        }

        // Show the button if the first visible item is past
        // the first item. We use a remembered derived state to
        // minimize unnecessary compositions
        val showButton by remember {
            derivedStateOf {
                listState.firstVisibleItemIndex > 0
            }
        }

        AnimatedVisibility(visible = showButton) {
            ScrollToTopButton()
        }
    }
}

অন্যান্য UI কম্পোজেবল আপডেট করার প্রয়োজন হলে কম্পোজিশনের অবস্থা সরাসরি পড়া কার্যকর, তবে এমন কিছু পরিস্থিতিও আছে যেখানে ইভেন্টটিকে একই কম্পোজিশনে পরিচালনা করার প্রয়োজন হয় না। এর একটি সাধারণ উদাহরণ হল ব্যবহারকারী একটি নির্দিষ্ট বিন্দু অতিক্রম করার পরে একটি বিশ্লেষণ ইভেন্ট পাঠানো। এটি দক্ষতার সাথে পরিচালনা করার জন্য, আমরা snapshotFlow() ব্যবহার করতে পারি:

val listState = rememberLazyListState()

LazyColumn(state = listState) {
    // ...
}

LaunchedEffect(listState) {
    snapshotFlow { listState.firstVisibleItemIndex }
        .map { index -> index > 0 }
        .distinctUntilChanged()
        .filter { it }
        .collect {
            MyAnalyticsService.sendScrolledPastFirstItemEvent()
        }
}

LazyListState বর্তমানে প্রদর্শিত সমস্ত আইটেম এবং স্ক্রিনে তাদের সীমানা সম্পর্কে তথ্য layoutInfo প্রপার্টির মাধ্যমে প্রদান করে। আরও তথ্যের জন্য LazyListLayoutInfo ক্লাসটি দেখুন।

স্ক্রোল অবস্থান নিয়ন্ত্রণ করা

স্ক্রোল পজিশনে প্রতিক্রিয়া জানানোর পাশাপাশি, অ্যাপগুলির জন্য স্ক্রোল পজিশন নিয়ন্ত্রণ করাও কার্যকর। LazyListState scrollToItem() ফাংশনের মাধ্যমে এটি সমর্থন করে, যা 'তাৎক্ষণিকভাবে' স্ক্রোল পজিশন স্ন্যাপ করে এবং animateScrollToItem() যা একটি অ্যানিমেশন (যা একটি মসৃণ স্ক্রোল নামেও পরিচিত) ব্যবহার করে স্ক্রোল করে:

@Composable
fun MessageList(messages: List<Message>) {
    val listState = rememberLazyListState()
    // Remember a CoroutineScope to be able to launch
    val coroutineScope = rememberCoroutineScope()

    LazyColumn(state = listState) {
        // ...
    }

    ScrollToTopButton(
        onClick = {
            coroutineScope.launch {
                // Animate scroll to the first item
                listState.animateScrollToItem(index = 0)
            }
        }
    )
}

বৃহৎ ডেটা-সেট (পেজিং)

পেজিং লাইব্রেরি অ্যাপগুলিকে আইটেমের বৃহৎ তালিকা সমর্থন করতে সক্ষম করে, প্রয়োজনে তালিকার ছোট ছোট অংশ লোড এবং প্রদর্শন করে। পেজিং 3.0 এবং পরবর্তী সংস্করণগুলি androidx.paging:paging-compose লাইব্রেরির মাধ্যমে কম্পোজ সমর্থন প্রদান করে।

পেজ করা কন্টেন্টের তালিকা প্রদর্শনের জন্য, আমরা collectAsLazyPagingItems() এক্সটেনশন ফাংশন ব্যবহার করতে পারি, এবং তারপর ফিরে আসা LazyPagingItems আমাদের LazyColumnitems() এ পাস করতে পারি। ভিউতে পেজিং সাপোর্টের মতো, আপনি item null কিনা তা পরীক্ষা করে ডেটা লোড হওয়ার সময় প্লেসহোল্ডার প্রদর্শন করতে পারেন:

@Composable
fun MessageList(pager: Pager<Int, Message>) {
    val lazyPagingItems = pager.flow.collectAsLazyPagingItems()

    LazyColumn {
        items(
            lazyPagingItems.itemCount,
            key = lazyPagingItems.itemKey { it.id }
        ) { index ->
            val message = lazyPagingItems[index]
            if (message != null) {
                MessageRow(message)
            } else {
                MessagePlaceholder()
            }
        }
    }
}

অলস লেআউট ব্যবহারের টিপস

আপনার Lazy লেআউটগুলি যাতে ইচ্ছামতো কাজ করে তা নিশ্চিত করার জন্য আপনি কয়েকটি টিপস বিবেচনা করতে পারেন।

০-পিক্সেল আকারের আইটেম ব্যবহার করা এড়িয়ে চলুন

এটি এমন পরিস্থিতিতে ঘটতে পারে যেখানে, উদাহরণস্বরূপ, আপনি পরবর্তী পর্যায়ে আপনার তালিকার আইটেমগুলি পূরণ করার জন্য চিত্রের মতো কিছু ডেটা অ্যাসিঙ্ক্রোনাসভাবে পুনরুদ্ধার করার আশা করেন। এর ফলে Lazy লেআউটটি প্রথম পরিমাপে তার সমস্ত আইটেম রচনা করবে, কারণ তাদের উচ্চতা 0 পিক্সেল এবং এটি ভিউপোর্টে সেগুলিকে ফিট করতে পারে। আইটেমগুলি লোড হয়ে গেলে এবং তাদের উচ্চতা প্রসারিত হয়ে গেলে, Lazy লেআউটগুলি তখন অন্যান্য সমস্ত আইটেম বাতিল করবে যা প্রথমবার অপ্রয়োজনীয়ভাবে তৈরি করা হয়েছিল কারণ সেগুলি আসলে ভিউপোর্টে ফিট করতে পারে না। এটি এড়াতে, আপনার আইটেমগুলিতে ডিফল্ট আকার নির্ধারণ করা উচিত, যাতে Lazy লেআউটটি ভিউপোর্টে আসলে কতগুলি আইটেম ফিট করতে পারে তার সঠিক গণনা করতে পারে:

@Composable
fun Item(imageUrl: String) {
    AsyncImage(
        model = rememberAsyncImagePainter(model = imageUrl),
        modifier = Modifier.size(30.dp),
        contentDescription = null
        // ...
    )
}

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

একই দিকে স্ক্রোলযোগ্য উপাদানগুলিকে নেস্ট করা এড়িয়ে চলুন

এটি শুধুমাত্র সেইসব ক্ষেত্রে প্রযোজ্য যেখানে স্ক্রোলযোগ্য শিশুরা পূর্বনির্ধারিত আকার ছাড়াই অন্য একই দিকের স্ক্রোলযোগ্য পিতামাতার ভিতরে নেস্ট করা হয়। উদাহরণস্বরূপ, একটি নির্দিষ্ট উচ্চতা ছাড়াই একটি শিশু LazyColumn একটি উল্লম্বভাবে স্ক্রোলযোগ্য Column পিতামাতার ভিতরে নেস্ট করার চেষ্টা করা:

// throws IllegalStateException
Column(
    modifier = Modifier.verticalScroll(state)
) {
    LazyColumn {
        // ...
    }
}

পরিবর্তে, একই ফলাফল অর্জন করা যেতে পারে আপনার সমস্ত কম্পোজেবলকে একটি প্যারেন্ট LazyColumn ভিতরে মোড়ানো এবং এর DSL ব্যবহার করে বিভিন্ন ধরণের কন্টেন্টে স্থানান্তর করার মাধ্যমে। এটি একক আইটেম, সেইসাথে একাধিক তালিকা আইটেম, এক জায়গায় নির্গত করতে সক্ষম করে:

LazyColumn {
    item {
        Header()
    }
    items(data) { item ->
        PhotoItem(item)
    }
    item {
        Footer()
    }
}

মনে রাখবেন যে যেখানে আপনি বিভিন্ন দিকের লেআউট নেস্ট করছেন, উদাহরণস্বরূপ, একটি স্ক্রোলযোগ্য প্যারেন্ট Row এবং একটি শিশু LazyColumn , সেগুলি অনুমোদিত:

Row(
    modifier = Modifier.horizontalScroll(scrollState)
) {
    LazyColumn {
        // ...
    }
}

সেইসাথে এমন ক্ষেত্রে যেখানে আপনি এখনও একই দিকের লেআউট ব্যবহার করেন, কিন্তু নেস্টেড শিশুদের জন্য একটি নির্দিষ্ট আকারও সেট করেন:

Column(
    modifier = Modifier.verticalScroll(scrollState)
) {
    LazyColumn(
        modifier = Modifier.height(200.dp)
    ) {
        // ...
    }
}

এক আইটেমে একাধিক উপাদান রাখার ব্যাপারে সতর্ক থাকুন

এই উদাহরণে, দ্বিতীয় আইটেম ল্যাম্বডা একটি ব্লকে 2টি আইটেম নির্গত করে:

LazyVerticalGrid(
    columns = GridCells.Adaptive(100.dp)
) {
    item { Item(0) }
    item {
        Item(1)
        Item(2)
    }
    item { Item(3) }
    // ...
}

অলস লেআউটগুলি প্রত্যাশা অনুযায়ী এটি পরিচালনা করবে - তারা একের পর এক উপাদানগুলিকে এমনভাবে সাজাবে যেন তারা ভিন্ন আইটেম। তবে, এটি করার ক্ষেত্রে কয়েকটি সমস্যা রয়েছে।

যখন একটি আইটেমের অংশ হিসেবে একাধিক উপাদান নির্গত হয়, তখন সেগুলিকে একটি সত্তা হিসেবে পরিচালনা করা হয়, যার অর্থ হল সেগুলিকে আর আলাদাভাবে তৈরি করা যায় না। যদি একটি উপাদান স্ক্রিনে দৃশ্যমান হয়, তাহলে আইটেমের সাথে সম্পর্কিত সমস্ত উপাদান তৈরি এবং পরিমাপ করতে হবে। অতিরিক্ত ব্যবহার করলে এটি কর্মক্ষমতা ক্ষতিগ্রস্ত করতে পারে। সমস্ত উপাদানকে একটি আইটেমে রাখার চরম ক্ষেত্রে, এটি Lazy লেআউট ব্যবহারের উদ্দেশ্যকে সম্পূর্ণরূপে ব্যর্থ করে। সম্ভাব্য কর্মক্ষমতা সমস্যা ছাড়াও, একটি আইটেমে আরও উপাদান স্থাপন করলে scrollToItem() এবং animateScrollToItem() এর সাথেও হস্তক্ষেপ হবে।

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

LazyVerticalGrid(
    columns = GridCells.Adaptive(100.dp)
) {
    item { Item(0) }
    item {
        Item(1)
        Divider()
    }
    item { Item(2) }
    // ...
}

কাস্টম ব্যবস্থা ব্যবহার করার কথা বিবেচনা করুন

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

এটি অর্জনের জন্য, আপনি কাস্টম উল্লম্ব Arrangement ব্যবহার করতে পারেন এবং এটি LazyColumn এ পাস করতে পারেন। নিম্নলিখিত উদাহরণে, TopWithFooter অবজেক্টকে শুধুমাত্র arrange পদ্ধতিটি বাস্তবায়ন করতে হবে। প্রথমত, এটি একের পর এক আইটেম স্থাপন করবে। দ্বিতীয়ত, যদি মোট ব্যবহৃত উচ্চতা ভিউপোর্টের উচ্চতার চেয়ে কম হয়, তবে এটি পাদলেখটি নীচে স্থাপন করবে:

object TopWithFooter : Arrangement.Vertical {
    override fun Density.arrange(
        totalSize: Int,
        sizes: IntArray,
        outPositions: IntArray
    ) {
        var y = 0
        sizes.forEachIndexed { index, size ->
            outPositions[index] = y
            y += size
        }
        if (y < totalSize) {
            val lastIndex = outPositions.lastIndex
            outPositions[lastIndex] = totalSize - sizes.last()
        }
    }
}

contentType যোগ করার কথা বিবেচনা করুন

আপনার Lazy লেআউটের কর্মক্ষমতা সর্বাধিক করার জন্য, Compose 1.2 দিয়ে শুরু করে, আপনার তালিকা বা গ্রিডে contentType যোগ করার কথা বিবেচনা করুন। এটি আপনাকে লেআউটের প্রতিটি আইটেমের জন্য সামগ্রীর ধরণ নির্দিষ্ট করতে দেয়, যেখানে আপনি একাধিক বিভিন্ন ধরণের আইটেমের সমন্বয়ে একটি তালিকা বা গ্রিড তৈরি করছেন:

LazyColumn {
    items(elements, contentType = { it.type }) {
        // ...
    }
}

যখন আপনি contentType প্রদান করেন, তখন Compose শুধুমাত্র একই ধরণের আইটেমগুলির মধ্যে রচনাগুলি পুনঃব্যবহার করতে সক্ষম হয়। যেহেতু একই ধরণের কাঠামোর আইটেমগুলি রচনা করার সময় পুনঃব্যবহার আরও কার্যকর, তাই সামগ্রীর ধরণগুলি প্রদান নিশ্চিত করে যে Compose সম্পূর্ণ ভিন্ন ধরণের B আইটেমের উপরে A ধরণের কোনও আইটেম রচনা করার চেষ্টা করে না। এটি রচনা পুনঃব্যবহারের সুবিধা এবং আপনার Lazy লেআউট কর্মক্ষমতা সর্বাধিক করতে সহায়তা করে।

কর্মক্ষমতা পরিমাপ

রিলিজ মোডে এবং R8 অপ্টিমাইজেশন সক্ষম থাকা অবস্থায় আপনি কেবলমাত্র Lazy লেআউটের কর্মক্ষমতা নির্ভরযোগ্যভাবে পরিমাপ করতে পারবেন। ডিবাগ বিল্ডগুলিতে, Lazy লেআউট স্ক্রোলিং ধীর হতে পারে। এই বিষয়ে আরও তথ্যের জন্য, Compose performance পড়ুন।

অতিরিক্ত সম্পদ

{% অক্ষরে অক্ষরে %} {% এন্ডভারব্যাটিম %} {% অক্ষরে অক্ষরে %} {% এন্ডভারব্যাটিম %}