Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jetpack Compose Cards in Column elevation increasing

I am using jetpack compose for Android and am trying to get multiple Cards with elevation inside a Column like this:

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

@Composable
fun Counter(
    modifier: Modifier = Modifier
) {
    Card(
        elevation = CardDefaults.cardElevation(
            defaultElevation = 6.dp
        ),
        modifier = modifier.height(100.dp),
    ) {
        Text("Example")
    }
}

@Preview(showBackground = true)
@Composable
fun CounterPreview() {
    Column(
        verticalArrangement = Arrangement.spacedBy(12.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier
            .padding(20.dp)
    ) {
        Counter()
        Counter()
        Counter()
        Counter()
        Counter()
    }
}

My Expected result would be, that every Card has the same shadow. Instead, the bottom cards have more shadow than the top one:

Image of Preview in Android Studio illustrating my issue

Is there some way I can get them to have an equal amount of shadow?

I already tried using LazyColumn, repeat, and setting a custom zIndex, but that didn't help.

like image 512
Letsric Avatar asked Dec 27 '25 14:12

Letsric


1 Answers

Android's material shadows are rendered using a lighting model with two sources: ambient light, and a spot light. You're seeing the effect of the spot shadows shifting out from under the items as they get further away from their light source at the top of the screen.

The only way to get perfectly even material shadows is to disable the spot component so that only the ambient shows. If that seems hacky or is unworkable for your setup, you're probably better off drawing your own shadows with a different technique, or maybe finding some library that does it. If you'd rather try to adjust the native ones, the following demonstrates a couple of different ways to do that.

Theme attributes solution

The generally simplest and most straightforward way to disable the spot is through a theme attribute, android:spotShadowAlpha. The ambient component is very light by default, so you'll probably also need to increase its alpha. For example:

<style name="Theme.App.AmbientShadows" parent="…">
    …
    <!-- Default spot value: 0.19 -->
    <item name="android:spotShadowAlpha">0.0</item>

    <!-- Default ambient value: 0.039 -->
    <item name="android:ambientShadowAlpha">0.1</item>

</style>

Unfortunately, these values are set on the Window's renderer, so they will affect all of the shadows in a given Activity or Dialog. If that's not acceptable, the workaround in code might still work for you.

Code solution

Spot shadow

It's often possible to disable the spot component on items individually. If you're already using the shadow() modifier directly, you can simply set its spotColor to Color.Transparent. On Composables with built-in shadows, though, you'd have to disable the inherent one first. For example, on a static Card:

Card(
    elevation = CardDefaults.cardElevation(0.dp, 0.dp, 0.dp, 0.dp, 0.dp),
    modifier = Modifier
        …
        .shadow(
            elevation = 16.dp,
            shape = CardDefaults.shape,
            spotColor = Color.Transparent
        )
) { … }

This simple setup won't work with animated elevations, however. If that's a requirement, you could put together something like the wrapped the Composable setup shown in my answer here.

Also, this fixes only the spot component; you'll likely still have to fiddle with the ambient. The android:ambientShadowAlpha theme attribute will work with this, but if you'd rather avoid the theme settings altogether, there's another way to intensify that component a bit.

Ambient shadow

These shadows are translucent, obviously, so stacking multiple ones has the same effect as increasing the alpha. Compose disregards more than one shadow() on a single Composable, but we can still stack the Composables themselves. For example:

@Composable
fun StackedCard(
    modifier: Modifier = Modifier,
    shape: Shape = CardDefaults.shape,
    colors: CardColors = CardDefaults.cardColors(),
    elevation: CardElevation = CardDefaults.cardElevation(),
    border: BorderStroke? = null,
    content: @Composable ColumnScope.() -> Unit
) {
    Box {
        Card(modifier, shape, colors, elevation, null) {}
        Card(modifier, shape, colors, elevation, border, content)
    }
}

This doesn't do anything with the shadows, so you'd use it like so:

StackedCard(
    elevation = CardDefaults.cardElevation(0.dp, 0.dp, 0.dp, 0.dp, 0.dp),
    modifier = Modifier
        …
        .shadow(
            elevation = 16.dp,
            shape = CardDefaults.shape,
            spotColor = Color.Transparent
        )
) { … }

The bottom Card could probably be replaced with a simpler Box or Layout and Modifier, and both functions could be combined or rearranged a little better, but this should give you the general idea.

I don't know that I would suggest this setup be used for shadows project-wide, but it's pretty handy for applying the fix specifically, without having to alter an entire Window. As you might suspect, you can stack as many Composables as you like, giving you some control over how dark it is.

Screenshot showing a regular shadow and a few fixed ones.

The leftmost item is a regular shadow; the other three show the code fix with an increasing number of stacked Cards.

like image 83
zed-alpha Avatar answered Dec 31 '25 05:12

zed-alpha



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!