Reputation: 7061
I have a screen where I need to show a header and a list of items wrapped in a card view. The whole screen has to be scrollable (like shown in the image below).
I know how to do this with a scrollable Column but I want to be able to use a LazyColumn
(because each list item will have its own ViewModel due to the complexity of the view and I thought LazyColumn will be more resources-efficient). For the header, I can use item
and for the list, I can use items
. Below is the code I tried:
@Composable
fun Screen(
items: List<String>
) {
Column(
Modifier.fillMaxSize()
) {
TopAppBar(title = { Text(text = "My Activity") })
LazyColumn {
// Header
item {
Text("Title", Modifier.padding(32.dp))
}
// I cannot use Box in this way here
Box(Modifier.padding(32.dp)) {
Card {
items(items.size) {
Text("Item $it")
}
}
}
}
}
}
The problem with that code is that I cannot wrap the list items in a card view because Card
is not a LazyListScope
. Using LazyColumn, how can I wrap the list items in a Card?
Upvotes: 4
Views: 2552
Reputation: 11
@Composable
fun TariffsScreen(viewModel: MobileTariffsViewModel, modifier: Modifier = Modifier) {
val pullToRefreshState = rememberPullToRefreshState()
val lazyListState = rememberLazyListState()
Box(
modifier = modifier.nestedScroll(pullToRefreshState.nestedScrollConnection)
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
state = lazyListState
) {
item {
Text(text = "Title")
}
mobileTariffItemContent(viewModel = viewModel)
}
}
}
fun LazyListScope.mobileTariffItemContent(
viewModel: MobileTariffsViewModel
) {
item {
val mobileTariffCombine by viewModel.mobileTariffCombine.collectAsStateWithLifecycle(emptyList())
ElevatedCard(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp)
) {
Column(
modifier = Modifier
.background(Color.White)
) {
mobileTariffCombine.forEachIndexed { index, tariff ->
MobileTariffItem(
tariff = tariff,
viewModel = viewModel
)
}
}
}
}
}
Upvotes: 0
Reputation: 29
Alternatively you can do this,
Column(
Modifier.fillMaxSize()
) {
TopAppBar(title = { Text(text = "My Activity") })
LazyColumn {
// Header
item {
Text("Title", Modifier.padding(32.dp))
}
// I cannot use Box in this way here
item {
Card {
items.forEach {
Text("Item $it")
}
}
}
}
}
Upvotes: 0
Reputation: 923
I was searching for the same but unfortunately couldn't find a solution so following a suggestion by @JanBína under another similar question I have customized each item card and got desired output and the code looks like this
@Composable
fun TransactionItem(
transaction: Transaction,
index: Int = 0,
totalSize: Int = 1,
onTransactionClicked: (Transaction) -> Unit
) {
val transactionDateTime = Instant.fromEpochMilliseconds(transaction.date).toLocalDateTime(
TimeZone.currentSystemDefault()
)
val cardMod = Modifier
.padding(horizontal = 5.dp)
.padding(bottom = if (index == totalSize - 1) 5.dp else 0.dp)
.fillMaxWidth()
.padding(horizontal = 5.dp)
if (totalSize == 1) {
cardMod.padding(vertical = 5.dp)
} else when (index) {
0 -> cardMod.padding(top = 5.dp)
totalSize - 1 -> cardMod.padding(bottom = 10.dp)
else -> cardMod.padding(vertical = 0.dp)
}
ElevatedCard(
onClick = {
onTransactionClicked(transaction)
},
modifier = cardMod,
shape = when (totalSize) {
1 -> RoundedCornerShape(15.dp)
else -> when (index) {
0 -> RoundedCornerShape(topStart = 15.dp, topEnd = 15.dp)
totalSize - 1 -> RoundedCornerShape(bottomStart = 15.dp, bottomEnd = 15.dp)
else -> RoundedCornerShape(0)
}
}
) {
///Item Content goes here
}
}
this is not the final solution I am still searching for one where we can achieve the same using a single card, final result looks like this
Upvotes: 0
Reputation: 1
As a workaround, you can emulate Card by drawing a custom Shape.
It would look something like this: screen_record
sealed class Item
class HeaderItem(...): Item()
class ContentItem(...) : Item()
...
val items: SnapshotStateList<Item> = mutableStateListOf()
...
LazyColumn(
modifier = Modifier.fillMaxSize(),
) {
itemsIndexed(
items = state.items,
) { index, item ->
val prevItem = state.items.getOrNull(index - 1)
val nextItem = state.items.getOrNull(index + 1)
Column {
when (item) {
is HeaderItem -> Header(item)
is ContentItem -> {
Box(
modifier = Modifier
.heightIn(min = 48.dp)
.fillMaxWidth()
.clip(shape = getShape(prevItem, nextItem, 16.dp))
.background(Color.Green.copy(0.3F))
) {
Item(item)
}
}
}
}
}
fun getShape(prevItem: Item?, nextItem: Item?, corner: Dp): Shape {
return if (prevItem is ContentItem && nextItem is ContentItem) {
//FLAT
RectangleShape
} else if (prevItem !is ContentItem && nextItem !is ContentItem) {
//ROUNDED_ALL
RoundedCornerShape(corner)
} else if (prevItem !is ContentItem) {
//ROUNDED_TOP
RoundedCornerShape(topStart = corner, topEnd = corner)
} else {
//ROUNDED_BOTTOM
RoundedCornerShape(bottomStart = corner, bottomEnd = corner)
}
}
Upvotes: 0
Reputation: 76534
You had it slightly mixed up, you should put the Card
inside of the items
call:
LazyColumn {
// Header
item {
Text("Title", Modifier.padding(32.dp))
}
items(items.size) {
// You can use Box in this way here
Box(Modifier.padding(32.dp)) {
Card {
Text("Item $it")
}
}
}
}
Upvotes: -1