Reputation: 175
I have a simple list of items that have two string values title and subtitle. This items should be displayed in a horizontal pager. Pager height is always limited and not always the same. The list of items should be automatically divided into pages depending on their height and available height of the pager.
For example, if I have 10 items and there can only be placed first 3 items on one page, then add new page and place next items.
The Page composable looks like this:
@Composable
fun ItemsPage(items: List<Item>) {
Column {
items.forEach {
SingleItem(item = it)
}
}
}
Composable for single item looks like this:
@Composable
fun SingleItem(item: Item) {
Column {
Text(
text = item.title,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
Text(
text = item.subtitle,
maxLines = 2,
color = Color.LightGray,
overflow = TextOverflow.Ellipsis
)
}
}
The problem is that depending on String length, Text can be up to 2 lines and I don't know what items and how many will have 2 lines of text.
Current output:
Expected output:
I had this previously as a ViewItem in ViewPager2 and RecyclerView.Adapter and I solved it by inflating ViewItem XML, populating text views with text and measuring root view with View.measure(widthMeasureSpec, heightMeasureSpec)
. But now I need to migrate it to compose.
Is there anything similar in compose or any other solution? How can I measure each composable without actually showing/drawing it and efficiently, avoiding too many re-renders?
Upvotes: 1
Views: 1784
Reputation: 422
You need to use Layout() to eliminate unfit items. You can also check this post. Here is our example:
MainScreen:
data class Item(val title: String, val subTitle: String)
data class ContainerItem(val placeable: Placeable, val yPosition: Int)
data class RowItem(val count: Int,var totalCount: Int)
@Composable
fun MainPage() {
val items = remember{
mutableStateListOf(
Item("Title1", "subTitle"),
Item("Title2", "subTitle"),
Item("Title3", "subTitle"),
Item("Title4", "subTitle"),
Item("Title5", "subTitle"),
Item("Title6", "subTitle"),
Item("Title7", "subTitle"),
)
}
val rowSize = remember { mutableStateOf(1)}
val rowItems = remember { mutableStateListOf<RowItem>()}
Box {
Row {
repeat(rowSize.value){
Column(Modifier
.height(290.dp)
.background(Color.Red))
{
//Keep index for each row
val index = remember { mutableStateOf(it)}
VerticalContainer(modifier = Modifier.background(Color.Green),
onPlacementComplete = { count ->
//Find total Count
var totalCount = count
for (i in rowItems) {
totalCount += i.count
}
//Add Item
rowItems.add(RowItem(count = count,totalCount))
//Increase row count
if (totalCount < items.size){
rowSize.value += 1
}
}) {
val start = remember { mutableStateOf(0)}
//Detect start value
start.value = if (index.value == 0) 0 else rowItems[index.value-1].totalCount
for (item in items.slice(start.value until items.size)) {
SingleItem(item)
}
}
}
}
}
}
}
Vertical Container:
@Composable
fun VerticalContainer(
modifier: Modifier = Modifier,
onPlacementComplete: (Int) -> Unit,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
val placeables = measurables.map { it.measure(constraints) };
val items = mutableListOf<ContainerItem>()
var yPosition = 0
for (placeable in placeables) {
if (yPosition + placeable.height > constraints.maxHeight) break
items.add(ContainerItem(placeable, yPosition))
yPosition += placeable.width
}
layout(
width = items.maxOf { it.placeable.width },
height = items.last().let { it.yPosition + it.placeable.height }
) {
items.forEach {
it.placeable.place(0, it.yPosition)
}
onPlacementComplete(items.count())
}
}
}
Single Item:
@Composable
fun SingleItem(item: Item) {
Column {
Text(
text = item.title,
maxLines = 2,
)
Text(
text = item.subTitle,
maxLines = 2,
color = Color.Gray,
)
}
}
ScreenShot:
Upvotes: 0