'Sticky headers like android contacts in Jetpack Compose
I didn't find any way to have a stickyHeader on the same line as an item in a LazyColumn:
So I used a Box to put the letter with its background on top of the LazyColumn and used the LazyListState to put it in the right position :
@Preview
@Composable
fun InlineStickyList() {
val unsorted = listOf(...) // Pair("FirstName","LastName")
val data = unsorted.sortedBy { it.second[0] }
val groups = data.groupBy { it.second[0] }
@Composable
fun Person(pair: Pair<String, String>, showLetter: Boolean) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(Color.White),
verticalAlignment = Alignment.CenterVertically
) {
Text(
"${pair.second[0]}",
modifier = Modifier
.padding(10.dp)
.alpha(if (showLetter) 1f else 0f),
fontSize = 20.sp
)
Text("${pair.first} ${pair.second}")
}
}
val state = rememberLazyListState()
Box(modifier = Modifier.fillMaxSize()) {
LazyColumn(modifier = Modifier.fillMaxSize(), state = state) {
groups.forEach { (_, group) ->
item {
Person(group[0], true)
}
items(group.subList(1, group.size)) {
Person(it, false)
}
}
}
}
val letter =
data[state.firstVisibleItemIndex].second[0]
val next =
data[state.firstVisibleItemIndex + 1].second[0]
Text(
"$letter",
fontSize = 20.sp,
modifier = Modifier
.clipToBounds()
.offset(y = if (letter != next) -with(LocalDensity.current) {
state.firstVisibleItemScrollOffset.toDp()
} else 0.dp)
.background(Color.White)
.padding(start = 10.dp, top = 10.dp, bottom = 10.dp),
)
}
Is there another way to achieve that ? (cleaner/better)
Solution 1:[1]
I did a custom implementation... Not sure if it's the best solution, but here it is...
@Composable
fun LetterHeader(char: String, modifier: Modifier = Modifier) {
Text(
text = char,
color = Color(117, 137, 199),
fontSize = 28.sp,
modifier = modifier.padding(horizontal = 16.dp, vertical = 8.dp)
)
}
There's nothing special in the function above. It represents a single char which acts as a "sticky header", but it will be also used in the item list as we can see below.
@Composable
fun NameItem(
name: String,
showCharHeader: Boolean,
modifier: Modifier
) {
Row(Modifier.fillMaxWidth()) {
if (showCharHeader) {
LetterHeader(
char = name.first().toString(),
modifier = modifier
)
} else {
Spacer(modifier = modifier)
}
Text(
text = name,
color = Color.DarkGray,
fontSize = 14.sp,
modifier = Modifier.fillMaxWidth().padding(16.dp)
)
}
}
The function above represents each list item. Notice we're reusing the LetterHeader
here. It will be displayed conditionally.
Finally, let's see the list...
@Composable
fun ListWithCustomStickHeaderScreen() {
Box(Modifier.fillMaxSize()) {
// List of names grouped by first char
val groupedNames = remember(names) {
names.groupBy { it.first() }
}
// Start indexes in the names list for each char
val startIndexes = remember(names) {
getStartIndexes(groupedNames.entries)
}
// End indexes in the names list for each char
val endIndexes = remember(names) {
getEndIndexes(groupedNames.entries)
}
// This commonModifier is used for both
// NameItem and LetterHeader
val commonModifier = Modifier.width(50.dp)
val listState = rememberLazyListState()
// We're going to move the stick header up in case of the
// first visible list index is one of last indexes
val moveStickyHeader by remember {
derivedStateOf {
endIndexes.contains(listState.firstVisibleItemIndex + 1)
}
}
LazyColumn(
modifier = Modifier.fillMaxSize(),
state = listState,
) {
itemsIndexed(names) { index, name ->
NameItem(
name,
// Showing the char header in the list item
// just in case it is one of the start indexes
// and it is not the first visible index
showCharHeader = startIndexes.contains(index) && listState.firstVisibleItemIndex != index,
commonModifier
)
}
}
LetterHeader(
char = names[listState.firstVisibleItemIndex].first().toString(),
modifier = commonModifier.then(
// Moving up the sticky header using offset modifier.
if (moveStickyHeader) {
Modifier.offset {
IntOffset(0, -listState.firstVisibleItemScrollOffset)
}
} else {
Modifier
}
)
)
}
}
The full code is available here.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |