I am trying to display a shadow at the start and end of a list to indicate that the list still has contents to the left/right.
I want the shadow to be as tall as the whole row so therefore I use Modifier.fillMaxHeight()
. Unfortunately this doesn't work unless I specifiy the size of the component at call time.
When I call the function as:
LazyRowWithShadows(modifier = Modifier
.fillMaxWidth(),
contentPadding = PaddingValues(horizontal = 12.dp),
shadowColor = colorResource(R.color.blue_white),
shadowAlphaStart = 0f, shadowAlphaEnd = 0.8f,
shadowWidth = 48.dp
) {
// Content
}
The shadows are not displayed, but the rest of the content is. I have to give the modifier a height
to make it work eg:
LazyRowWithShadows(modifier = Modifier
.fillMaxWidth()
.height(X.dp), // This is what makes it display, but requires knowing the height of the row
contentPadding = PaddingValues(horizontal = 12.dp),
shadowColor = colorResource(R.color.blue_white),
shadowAlphaStart = 0f, shadowAlphaEnd = 0.8f,
shadowWidth = 48.dp
) {
// Content
}
I would prefer if the LazyRowWithShadows
handled displaying the shadows even if no size is specified when calling it. Is there an easy way to do this?
This is the code for the LazyRowWithShadows
:
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/** A LazyRow that shows start & end shadows when content is not fully scrolled. */
@ExperimentalAnimationApi
@Composable
fun LazyRowWithShadows(
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(0.dp),
shadowColor: Color = Color.Black,
shadowAlphaStart: Float = 0f,
shadowAlphaEnd: Float = 0.3f,
shadowWidth: Dp = 32.dp,
content: LazyListScope.() -> Unit
) {
val listState = rememberLazyListState()
val showStartShadow by remember {
derivedStateOf {
listState.firstVisibleItemScrollOffset > 0 || listState.firstVisibleItemIndex > 0
}
}
val showEndShadow by remember {
derivedStateOf {
val lastItemIfVisible = listState.layoutInfo.visibleItemsInfo.maxByOrNull { it.index }?.takeIf { it.index == listState.layoutInfo.totalItemsCount - 1 }
if (lastItemIfVisible != null) {
val lastItemEndX = lastItemIfVisible.offset lastItemIfVisible.size
lastItemEndX > listState.layoutInfo.viewportEndOffset
} else {
true
}
}
}
Box(modifier = modifier) {
LazyRow(
state = listState,
contentPadding = contentPadding,
content = content)
// Start scroll shadow
AnimatedVisibility(visible = showStartShadow, modifier = Modifier.align(Alignment.CenterStart)) {
Box(modifier = Modifier
.fillMaxHeight()
.width(shadowWidth)
.background(brush = Brush.horizontalGradient(colors = listOf(shadowColor.copy(alpha = shadowAlphaEnd), shadowColor.copy(alpha = shadowAlphaStart))))
)
}
// End scroll shadow
AnimatedVisibility(visible = showEndShadow, modifier = Modifier.align(Alignment.CenterEnd)) {
Box(modifier = Modifier
.fillMaxHeight()
.width(shadowWidth)
.background(brush = Brush.horizontalGradient(colors = listOf(shadowColor.copy(alpha = shadowAlphaStart), shadowColor.copy(alpha = shadowAlphaEnd))))
)
}
}
}
Edit: I have tried the solution proposed in fillMaxSize modifier not working when combined with VerticalScroll in Jetpack Compose, but it didn't solve my issue. The changes I made to try to solve it using that answer as inspiration are:
BoxWithConstraints(modifier = modifier) { // Changed to BoxWithConstraints
LazyRow(
state = listState,
contentPadding = contentPadding,
content = content)
// Start scroll shadow
AnimatedVisibility(visible = showStartShadow, modifier = Modifier.align(Alignment.CenterStart)) {
Box(modifier = Modifier
.height(this@BoxWithConstraints.maxHeight) // Changed from fillMaxHeight
.width(shadowWidth)
.background(brush = Brush.horizontalGradient(colors = listOf(shadowColor.copy(alpha = shadowAlphaEnd), shadowColor.copy(alpha = shadowAlphaStart))))
)
}
// End scroll shadow
AnimatedVisibility(visible = showEndShadow, modifier = Modifier.align(Alignment.CenterEnd)) {
Box(modifier = Modifier
.height(this@BoxWithConstraints.maxHeight) // Changed from fillMaxHeight
.width(shadowWidth)
.background(brush = Brush.horizontalGradient(colors = listOf(shadowColor.copy(alpha = shadowAlphaStart), shadowColor.copy(alpha = shadowAlphaEnd))))
)
}
}
I also tried adding a height
to the AnimatedVisibility
elements, but that didn't change anything.
CodePudding user response:
After trying out a couple of ways, using Modifier.onSizeChanged
to get the height
of LazyRow
finally made it work.
Convert this height
to dp
and pass it to Modifier.height
for shadow's Box(s).
Box(modifier = modifier) {
val density = LocalDensity.current
var height by remember { mutableStateOf(0) }
val heightDp = remember(height) { with(density){ height.toDp() } }
LazyRow(
modifier = Modifier.onSizeChanged {
height = it.height
},
state = listState,
contentPadding = contentPadding,
content = content)
// Start scroll shadow
AnimatedVisibility(visible = showStartShadow, modifier = Modifier.align(Alignment.CenterStart)) {
Box(modifier = Modifier
.height(heightDp)
.width(shadowWidth)
.background(brush = Brush.horizontalGradient(colors = listOf(shadowColor.copy(alpha = shadowAlphaEnd), shadowColor.copy(alpha = shadowAlphaStart))))
)
}
// End scroll shadow
AnimatedVisibility(visible = showEndShadow, modifier = Modifier.align(Alignment.CenterEnd)) {
Box(modifier = Modifier
.height(heightDp)
.width(shadowWidth)
.background(brush = Brush.horizontalGradient(colors = listOf(shadowColor.copy(alpha = shadowAlphaStart), shadowColor.copy(alpha = shadowAlphaEnd))))
)
}
}