Outline spot shadow has unwanted vertical offset if view shape has uneven corners and is used as a RecyclerView item

2 days ago 7
ARTICLE AD BOX

I'm facing a layout issue with a vertical RecyclerView whose items use are MaterialCards with custom shapeAppearanceOverlay which has asymmetric corners (top-left corner radius = 0dp, all other corners = 16dp).

When the cards are tall and appear near the bottom of the screen, the outline spot shadow becomes noticeably vertically shifted. I understand this is how Android renders shadows, but the offset becomes too large in this specific case, and produces visual artifacts.

enter image description here enter image description here

This issue is reproducible in minimal project: Activity + RecyclerView. Just create new project in Android studio and add the code from the sections below:

class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) setupRecycler(generateItems()) } private fun generateItems(): List<CardAdapter.CardItem> = List(15) { index -> CardAdapter.CardItem( title = "Card ${index + 1}", description = buildString { repeat((15..40).random()) { append("Test line\n") } } ) } private fun setupRecycler(cardItems: List<CardAdapter.CardItem>) = binding.recyclerView.apply { layoutManager = LinearLayoutManager(context) adapter = CardAdapter(cardItems) val spacingInPixels = resources.getDimensionPixelSize(R.dimen.card_spacing) addItemDecoration(SpacingItemDecoration(spacingInPixels)) } } <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context=".MainActivity"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false" android:clipToPadding="false" /> </LinearLayout> class CardAdapter(private val items: List<CardItem>) : RecyclerView.Adapter<CardAdapter.CardViewHolder>() { data class CardItem( val title: String, val description: String ) inner class CardViewHolder(private val binding: ItemCardBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(item: CardItem) { binding.cardTitle.text = item.title binding.cardDescription.text = item.description } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder { val binding = ItemCardBinding.inflate(LayoutInflater.from(parent.context), parent, false) return CardViewHolder(binding) } override fun onBindViewHolder(holder: CardViewHolder, position: Int) { holder.bind(items[position]) } override fun getItemCount(): Int = items.size } <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:clipChildren="false" android:clipToPadding="false" android:layout_width="match_parent" android:layout_height="wrap_content"> <com.google.android.material.card.MaterialCardView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="16dp" android:outlineSpotShadowColor="#FF0000" app:cardElevation="6dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:shapeAppearanceOverlay="@style/ShapeAppearance.App.Card"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> <TextView android:id="@+id/card_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Card Title" android:textAppearance="?attr/textAppearanceHeadline6" android:textStyle="bold" /> <TextView android:id="@+id/card_description" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="This is a material card with a minimum height of 200dp" android:textAppearance="?attr/textAppearanceBody2" /> </LinearLayout> </com.google.android.material.card.MaterialCardView> </androidx.constraintlayout.widget.ConstraintLayout> <style name="ShapeAppearance.App.Card" parent=""> <item name="cornerFamily">rounded</item> <item name="cornerSizeTopLeft">0dp</item> <item name="cornerSizeTopRight">16dp</item> <item name="cornerSizeBottomLeft">16dp</item> <item name="cornerSizeBottomRight">16dp</item> </style>

What I've tried:

Replaced MaterialCardView with a regular LinearLayout + shape drawable background → shadow issue still occurs (so it doesn't seem to be a MaterialCardView bug) Tested multiple versions of the Material Components library Tried XML attributes such as: app:useCompatPadding="true" app:preventCornerOverlap="true" android:clipToPadding="false" / clipToOutline variations Experimented with different elevations, padding, and outline settings None of these helped — the shadow still gets pushed downward too far when the view has uneven corner radii.

Question: Is there any known workaround to prevent or reduce this vertical shadow offset when using asymmetric rounded-corner shapes as items in a RecyclerView? Any suggestions or insights would be greatly appreciated.

Read Entire Article