Dylan
09/10/2021, 2:13 PMTopAppBar
based on the scroll position within a LazyList
. The TopAppBar
is within a Scaffold
, and the Scaffold content is a NavGraph
. This NavGraph contains a composable which contains my LazyList. I tried to do it by creating an AppBarState
which holds the current elevation as a MutableState<Float>
and also the elevationRange (0f..4f
). I also created a LocalAppBarState
which holds the current AppBarState
. The elevation of my TopAppBar
is defined using LocalAppBarState.current.elevation.dp
.
To define the elevation I created a composable that return a LazyListState
. It uses the LocalAppBarState
to define the calculated elevation based on the LazyListState
.
Unfortunately, doing this cause my app to lag when scrolling. But I'm not sure I know any other way to do it. I read some things about Modifier.graphicsLayer
, but even after trying that the issue remains the same. Is there a better way to do it?
I will leave as the first comment in this thread the code for all of that.Dylan
09/10/2021, 2:14 PMclass AppBarState {
var elevationRange = 0f..4f
var elevation by mutableStateOf(elevationRange.start)
}
@Composable
fun rememberAppBarState(): AppBarState = remember { AppBarState() }
val LocalAppBarState = compositionLocalOf<AppBarState> { error("LocalAppBarState not provided") }
@Composable
fun rememberAppBarAwareLazyListState(
initialFirstVisibleItemIndex: Int = 0,
initialFirstVisibleItemScrollOffset: Int = 0
): LazyListState {
val appBarState = LocalAppBarState.current
val lazyListState =
rememberLazyListState(initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset)
if (lazyListState.isScrollInProgress) {
val elevation = if (lazyListState.firstVisibleItemIndex == 0) {
(lazyListState.firstVisibleItemScrollOffset
.toFloat() / OffsetDivider)
.coerceIn(appBarState.elevationRange)
} else {
appBarState.elevationRange.endInclusive
}
appBarState.elevation = elevation
}
return lazyListState
}
adjpd
09/10/2021, 2:40 PMDylan
09/10/2021, 2:41 PMgeorgiy.shur
09/10/2021, 4:10 PMprivate val MAX_OFFSET = 16.dp
private val MAX_ELEVATION = AppBarDefaults.TopAppBarElevation
private fun calculateTopBarElevation(
offset: Float,
maxOffset: Float,
maxElevation: Float,
): Float {
return offset.coerceIn(0f..maxOffset) * maxElevation / maxOffset
}
@Composable
fun rememberTopBarElevation(
scrollState: ScrollState
): Dp {
val offset = scrollState.value
return rememberTopBarElevation(offset = offset.toFloat())
}
@Composable
fun rememberTopBarElevation(
lazyListState: LazyListState
): Dp {
with(LocalDensity.current) {
/*
Unfortunately for LazyListState there is no way to get the offset from
the beginning of the list, only from the beginning of the last visible
item. That's why we're using this workaround. It won't work 100% correctly
if the first item will be smaller than MAX_OFFSET, but it shouldn't
happen in practice.
*/
val offset = if (lazyListState.firstVisibleItemIndex == 0) {
lazyListState.firstVisibleItemScrollOffset
} else MAX_OFFSET.toPx()
return rememberTopBarElevation(offset = offset.toFloat())
}
}
@Composable
private fun rememberTopBarElevation(
offset: Float
): Dp {
with(LocalDensity.current) {
val maxOffset = remember { MAX_OFFSET.toPx() }
val maxElevation = remember { MAX_ELEVATION.toPx() }
return remember(offset) {
calculateTopBarElevation(
offset = offset,
maxOffset = maxOffset,
maxElevation = maxElevation,
).toDp()
}
}
}
And then, the usage:
val listScrollState = rememberLazyListState()
Scaffold(
...,
topBar = {
AppBar(
...,
elevation = rememberTopBarElevation(listScrollState),
)
}
) {
LazyPagingColumn(
...,
lazyListState = listScrollState
)
}
georgiy.shur
09/10/2021, 4:11 PMLazyListState
and ScrollState
, but you may just ignore the secondDylan
09/10/2021, 4:58 PM