Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] Elevation (elevation style prop) looks weird combined with border radius (borderRadius style prop) on RN 0.77 #48874

Closed
hrastnik opened this issue Jan 22, 2025 · 20 comments
Labels
Issue: Author Provided Repro This issue can be reproduced in Snack or an attached project. Platform: Android Android applications. Resolution: Fixed A PR that fixes this issue has been merged.

Comments

@hrastnik
Copy link

hrastnik commented Jan 22, 2025

Description

When the elevation prop is applied to a View or elements like TouchableOpacity, a shadow is expected. However, since version 0.77, the shadow appears distorted, as if the borderRadius is too low.

To correct this, the borderRadius must be increased to ~3 times its original value. For example, if the button height is 50 and the borderRadius should normally be 25 for an oval shape, it now needs to be adjusted to 75 to look correct.

Steps to reproduce

  1. Create a new project.
  2. Add this code to App.tsx (screenshot 1)
    <View style={{padding: 64, flex: 1, backgroundColor: 'white'}}>
      <View
        style={{
          backgroundColor: '#7f7f7f',
          justifyContent: 'center',
          alignItems: 'center',
          height: 50,
          borderRadius: 25,
          elevation: 2,
        }}>
        <Text style={{color: 'white'}}>Button</Text>
      </View>
    </View>
  1. Observe the issue (screenshot 2)

React Native Version

0.77.0

Affected Platforms

Runtime - Android

Output of npx react-native info

System:
  OS: macOS 15.2
  CPU: (12) arm64 Apple M2 Pro
  Memory: 171.84 MB / 16.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 20.14.0
    path: ~/.nvm/versions/node/v20.14.0/bin/node
  Yarn:
    version: 1.22.22
    path: ~/.nvm/versions/node/v20.14.0/bin/yarn
  npm:
    version: 10.9.0
    path: ~/.nvm/versions/node/v20.14.0/bin/npm
  Watchman:
    version: 2024.07.15.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.16.2
    path: /var/folders/ht/h1xcpc3x137gqf6d8dn3z9l40000gn/T/frum_71903_1737582017493/bin/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 24.2
      - iOS 18.2
      - macOS 15.2
      - tvOS 18.2
      - visionOS 2.2
      - watchOS 11.2
  Android SDK: Not Found
IDEs:
  Android Studio: 2024.2 AI-242.23339.11.2421.12483815
  Xcode:
    version: 16.2/16C5032a
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.9
    path: /usr/bin/javac
  Ruby:
    version: 2.7.6
    path: /var/folders/ht/h1xcpc3x137gqf6d8dn3z9l40000gn/T/frum_71903_1737582017493/bin/ruby
npmPackages:
  "@react-native-community/cli":
    installed: 15.0.1
    wanted: 15.0.1
  react:
    installed: 18.3.1
    wanted: 18.3.1
  react-native:
    installed: 0.77.0
    wanted: 0.77.0
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: true
  newArchEnabled: true

Stacktrace or Logs

-

Reproducer

https://github.com/hrastnik/rn-77-border-radius-elevation-repro

Screenshots and Videos

Screenshot 1 - code:
Image

Screenshot 2 - output
Image

@hoanghai9650
Copy link

hoanghai9650 commented Jan 23, 2025

same here!

@cortinico
Copy link
Contributor

@joevilches @NickGerleman is this related to recent changes to border* props?

@NickGerleman
Copy link
Contributor

This implies background drawable outline is incorrect. @jorge-cab has been working around here recently, and I can ask.

@efstathiosntonas
Copy link

@NickGerleman hi, is there any update? Thank you

@migueldaipre migueldaipre added Issue: Author Provided Repro This issue can be reproduced in Snack or an attached project. and removed Needs: Triage 🔍 labels Jan 27, 2025
@jorge-cab
Copy link
Contributor

@efstathiosntonas getting a fix up now

@cortinico cortinico added the Resolution: PR Submitted A pull request with a fix has been provided. label Jan 28, 2025
@cortinico
Copy link
Contributor

Fixed by #48982

@cortinico cortinico added Resolution: Fixed A PR that fixes this issue has been merged. and removed Resolution: PR Submitted A pull request with a fix has been provided. labels Jan 28, 2025
@AakashThakur23102000
Copy link

What to do in my existing project to get this patch.

@efstathiosntonas
Copy link

efstathiosntonas commented Jan 28, 2025

@AakashThakur23102000 you need to build react native from source and use patch-package to apply the patch

edit:

https://reactnative.dev/contributing/how-to-build-from-source (do not point at nightly, just use 0.77)
https://www.npmjs.com/package/patch-package

@NickGerleman
Copy link
Contributor

NickGerleman commented Jan 29, 2025

Pick request for next 0.77 patch: reactwg/react-native-releases#761

@iM-GeeKy
Copy link

iM-GeeKy commented Feb 3, 2025

@NickGerleman I'm using the following react-native+0.77.0.patch

diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CompositeBackgroundDrawable.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CompositeBackgroundDrawable.kt
index 7105d09..816814d 100644
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CompositeBackgroundDrawable.kt
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CompositeBackgroundDrawable.kt
@@ -15,6 +15,7 @@ import android.graphics.drawable.Drawable
 import android.graphics.drawable.LayerDrawable
 import android.os.Build
 import com.facebook.react.common.annotations.UnstableReactNativeAPI
+import com.facebook.react.uimanager.PixelUtil.dpToPx
 import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
 import com.facebook.react.uimanager.style.BorderInsets
 import com.facebook.react.uimanager.style.BorderRadiusStyle
@@ -200,14 +201,14 @@ internal class CompositeBackgroundDrawable(
         pathForOutline.addRoundRect(
             RectF(bounds),
             floatArrayOf(
-                it.topLeft.horizontal + (computedBorderInsets?.left ?: 0f),
-                it.topLeft.vertical + (computedBorderInsets?.top ?: 0f),
-                it.topRight.horizontal + (computedBorderInsets?.right ?: 0f),
-                it.topRight.vertical + (computedBorderInsets?.top ?: 0f),
-                it.bottomRight.horizontal + (computedBorderInsets?.right ?: 0f),
-                it.bottomRight.vertical + (computedBorderInsets?.bottom ?: 0f),
-                it.bottomLeft.horizontal + (computedBorderInsets?.left ?: 0f),
-                it.bottomLeft.vertical) + (computedBorderInsets?.bottom ?: 0f),
+                (it.topLeft.horizontal + (computedBorderInsets?.left ?: 0f)).dpToPx(),
+                (it.topLeft.vertical + (computedBorderInsets?.top ?: 0f)).dpToPx(),
+                (it.topRight.horizontal + (computedBorderInsets?.right ?: 0f)).dpToPx(),
+                (it.topRight.vertical + (computedBorderInsets?.top ?: 0f)).dpToPx(),
+                (it.bottomRight.horizontal + (computedBorderInsets?.right ?: 0f)).dpToPx(),
+                (it.bottomRight.vertical + (computedBorderInsets?.bottom ?: 0f)).dpToPx(),
+                (it.bottomLeft.horizontal + (computedBorderInsets?.left ?: 0f)).dpToPx(),
+                (it.bottomLeft.vertical + (computedBorderInsets?.bottom ?: 0f)).dpToPx()),
             Path.Direction.CW)
       }

I've rebuilt my expo development client from source, but I'm still seeing the issue described above. Is there something I'm missing? Screenshots of the problem can be found in the open issue on react-native-paper-dates.

@samanjoy97
Copy link

I have also faced this issue where borderRadius and elevation works properly in React Native version 0.76.6 but in React Native version 0.77 it's breaks. How to fix this problem please suggest.

@Smit-Kadawala
Copy link

I also change in file (CompositeBackgroundDrawable) but's it's not work

my Style

editTextContainer: {
padding: 4,
elevation: 2,
borderRadius: 10,
flexDirection: 'row',
alignItems: 'center',
marginVertical: 5,
},

I just upgrade React native Version for 0.76.5 to 0.77

Image

@mihailapuste
Copy link

@NickGerleman I'm using the following react-native+0.77.0.patch

diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CompositeBackgroundDrawable.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CompositeBackgroundDrawable.kt
index 7105d09..816814d 100644
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CompositeBackgroundDrawable.kt
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CompositeBackgroundDrawable.kt
@@ -15,6 +15,7 @@ import android.graphics.drawable.Drawable
 import android.graphics.drawable.LayerDrawable
 import android.os.Build
 import com.facebook.react.common.annotations.UnstableReactNativeAPI
+import com.facebook.react.uimanager.PixelUtil.dpToPx
 import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
 import com.facebook.react.uimanager.style.BorderInsets
 import com.facebook.react.uimanager.style.BorderRadiusStyle
@@ -200,14 +201,14 @@ internal class CompositeBackgroundDrawable(
         pathForOutline.addRoundRect(
             RectF(bounds),
             floatArrayOf(
-                it.topLeft.horizontal + (computedBorderInsets?.left ?: 0f),
-                it.topLeft.vertical + (computedBorderInsets?.top ?: 0f),
-                it.topRight.horizontal + (computedBorderInsets?.right ?: 0f),
-                it.topRight.vertical + (computedBorderInsets?.top ?: 0f),
-                it.bottomRight.horizontal + (computedBorderInsets?.right ?: 0f),
-                it.bottomRight.vertical + (computedBorderInsets?.bottom ?: 0f),
-                it.bottomLeft.horizontal + (computedBorderInsets?.left ?: 0f),
-                it.bottomLeft.vertical) + (computedBorderInsets?.bottom ?: 0f),
+                (it.topLeft.horizontal + (computedBorderInsets?.left ?: 0f)).dpToPx(),
+                (it.topLeft.vertical + (computedBorderInsets?.top ?: 0f)).dpToPx(),
+                (it.topRight.horizontal + (computedBorderInsets?.right ?: 0f)).dpToPx(),
+                (it.topRight.vertical + (computedBorderInsets?.top ?: 0f)).dpToPx(),
+                (it.bottomRight.horizontal + (computedBorderInsets?.right ?: 0f)).dpToPx(),
+                (it.bottomRight.vertical + (computedBorderInsets?.bottom ?: 0f)).dpToPx(),
+                (it.bottomLeft.horizontal + (computedBorderInsets?.left ?: 0f)).dpToPx(),
+                (it.bottomLeft.vertical + (computedBorderInsets?.bottom ?: 0f)).dpToPx()),
             Path.Direction.CW)
       }

I've rebuilt my expo development client from source, but I'm still seeing the issue described above. Is there something I'm missing? Screenshots of the problem can be found in the open issue on react-native-paper-dates.

@NickGerleman I also just tried this patch and didnt work for me either.

@efstathiosntonas
Copy link

this patch works for me:

react-native+0.77.0.patch

Click me
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BackgroundStyleApplicator.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BackgroundStyleApplicator.kt
index 0040b6e..385b32a 100644
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BackgroundStyleApplicator.kt
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BackgroundStyleApplicator.kt
@@ -13,7 +13,6 @@ import android.graphics.Path
 import android.graphics.Rect
 import android.graphics.RectF
 import android.graphics.drawable.Drawable
-import android.graphics.drawable.LayerDrawable
 import android.os.Build
 import android.view.View
 import android.widget.ImageView
@@ -106,14 +105,15 @@ public object BackgroundStyleApplicator {
 
     composite.borderInsets = composite.borderInsets ?: BorderInsets()
     composite.borderInsets?.setBorderWidth(edge, width)
+    if (Build.VERSION.SDK_INT >= MIN_OUTSET_BOX_SHADOW_SDK_VERSION) {
+      for (shadow in composite.outerShadows.filterIsInstance<OutsetBoxShadowDrawable>()) {
+        shadow.borderRadius = composite.borderRadius
+      }
+    }
+
     if (Build.VERSION.SDK_INT >= MIN_INSET_BOX_SHADOW_SDK_VERSION) {
-      val innerShadows = composite.innerShadows
-      if (innerShadows != null) {
-        for (i in 0 until innerShadows.numberOfLayers) {
-          val shadow = innerShadows.getDrawable(i)
-          (shadow as InsetBoxShadowDrawable).borderInsets = composite.borderInsets
-          shadow.invalidateSelf()
-        }
+      for (shadow in composite.innerShadows.filterIsInstance<InsetBoxShadowDrawable>()) {
+        shadow.borderRadius = composite.borderRadius
       }
     }
   }
@@ -174,30 +174,16 @@ public object BackgroundStyleApplicator {
     }
 
     if (Build.VERSION.SDK_INT >= MIN_OUTSET_BOX_SHADOW_SDK_VERSION) {
-      val outerShadows = compositeBackgroundDrawable.outerShadows
-      if (outerShadows != null) {
-        for (i in 0 until outerShadows.numberOfLayers) {
-          val shadow = outerShadows.getDrawable(i)
-          if (shadow is OutsetBoxShadowDrawable) {
-            shadow.borderRadius = shadow.borderRadius ?: BorderRadiusStyle()
-            shadow.borderRadius?.set(corner, radius)
-            shadow.invalidateSelf()
-          }
-        }
+      for (shadow in
+          compositeBackgroundDrawable.outerShadows.filterIsInstance<OutsetBoxShadowDrawable>()) {
+        shadow.borderRadius = compositeBackgroundDrawable.borderRadius
       }
     }
 
     if (Build.VERSION.SDK_INT >= MIN_INSET_BOX_SHADOW_SDK_VERSION) {
-      val innerShadows = compositeBackgroundDrawable.innerShadows
-      if (innerShadows != null) {
-        for (i in 0 until innerShadows.numberOfLayers) {
-          val shadow = innerShadows.getDrawable(i)
-          if (shadow is InsetBoxShadowDrawable) {
-            shadow.borderRadius = shadow.borderRadius ?: BorderRadiusStyle()
-            shadow.borderRadius?.set(corner, radius)
-            shadow.invalidateSelf()
-          }
-        }
+      for (shadow in
+          compositeBackgroundDrawable.outerShadows.filterIsInstance<InsetBoxShadowDrawable>()) {
+        shadow.borderRadius = compositeBackgroundDrawable.borderRadius
       }
     }
 
@@ -291,14 +277,18 @@ public object BackgroundStyleApplicator {
       return
     }
 
-    var innerShadows: LayerDrawable? = null
-    var outerShadows: LayerDrawable? = null
+    var innerShadows = mutableListOf<InsetBoxShadowDrawable>()
+    var outerShadows = mutableListOf<OutsetBoxShadowDrawable>()
 
     val compositeBackgroundDrawable = ensureCompositeBackgroundDrawable(view)
     val borderInsets = compositeBackgroundDrawable.borderInsets
     val borderRadius = compositeBackgroundDrawable.borderRadius
 
-    for (boxShadow in shadows.asReversed()) {
+    /**
+     * z-ordering of user-provided shadow-list is opposite direction of LayerDrawable z-ordering
+     * https://drafts.csswg.org/css-backgrounds/#shadow-layers
+     */
+    for (boxShadow in shadows) {
       val offsetX = boxShadow.offsetX
       val offsetY = boxShadow.offsetY
       val color = boxShadow.color ?: Color.BLACK
@@ -307,10 +297,7 @@ public object BackgroundStyleApplicator {
       val inset = boxShadow.inset ?: false
 
       if (inset && Build.VERSION.SDK_INT >= MIN_INSET_BOX_SHADOW_SDK_VERSION) {
-        if (innerShadows == null) {
-          innerShadows = LayerDrawable(emptyArray())
-        }
-        innerShadows.addLayer(
+        innerShadows.add(
             InsetBoxShadowDrawable(
                 context = view.context,
                 borderRadius = borderRadius,
@@ -321,10 +308,7 @@ public object BackgroundStyleApplicator {
                 blurRadius = blurRadius,
                 spread = spreadDistance))
       } else if (!inset && Build.VERSION.SDK_INT >= MIN_OUTSET_BOX_SHADOW_SDK_VERSION) {
-        if (outerShadows == null) {
-          outerShadows = LayerDrawable(emptyArray())
-        }
-        outerShadows.addLayer(
+        outerShadows.add(
             OutsetBoxShadowDrawable(
                 context = view.context,
                 borderRadius = borderRadius,
@@ -336,8 +320,9 @@ public object BackgroundStyleApplicator {
       }
     }
 
-    view.background = ensureCompositeBackgroundDrawable(view).withNewOuterShadow(outerShadows)
-    view.background = ensureCompositeBackgroundDrawable(view).withNewInnerShadow(innerShadows)
+    view.background =
+        ensureCompositeBackgroundDrawable(view)
+            .withNewShadows(outerShadows = outerShadows, innerShadows = innerShadows)
   }
 
   @JvmStatic
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CSSBackgroundDrawable.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CSSBackgroundDrawable.java
index 506b9c8..8010d84 100644
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CSSBackgroundDrawable.java
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CSSBackgroundDrawable.java
@@ -390,9 +390,6 @@ public class CSSBackgroundDrawable extends Drawable {
     updatePath();
     canvas.save();
 
-    // Clip outer border
-    canvas.clipPath(Preconditions.checkNotNull(mOuterClipPathForBorderRadius), Region.Op.INTERSECT);
-
     // Draws the View without its border first (with background color fill)
     int useColor = ColorUtils.setAlphaComponent(mColor, (Color.alpha(mColor) * mAlpha) >> 8);
     if (Color.alpha(useColor) != 0) {
@@ -434,6 +431,10 @@ public class CSSBackgroundDrawable extends Drawable {
         || borderWidth.left > 0
         || borderWidth.right > 0) {
 
+      // Clip outer border
+      canvas.clipPath(
+              Preconditions.checkNotNull(mOuterClipPathForBorderRadius), Region.Op.INTERSECT);
+
       // If it's a full and even border draw inner rect path with stroke
       final float fullBorderWidth = getFullBorderWidth();
       int borderColor = getBorderColor(Spacing.ALL);
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CompositeBackgroundDrawable.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CompositeBackgroundDrawable.kt
index 7105d09..6cf3f85 100644
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CompositeBackgroundDrawable.kt
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CompositeBackgroundDrawable.kt
@@ -15,7 +15,7 @@ import android.graphics.drawable.Drawable
 import android.graphics.drawable.LayerDrawable
 import android.os.Build
 import com.facebook.react.common.annotations.UnstableReactNativeAPI
-import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
+import com.facebook.react.uimanager.PixelUtil.dpToPx
 import com.facebook.react.uimanager.style.BorderInsets
 import com.facebook.react.uimanager.style.BorderRadiusStyle
 
@@ -33,7 +33,7 @@ internal class CompositeBackgroundDrawable(
     public val originalBackground: Drawable? = null,
 
     /** Non-inset box shadows */
-    outerShadows: LayerDrawable? = null,
+    public val outerShadows: List<Drawable> = emptyList(),
 
     /**
      * CSS background layer and border rendering
@@ -44,144 +44,143 @@ internal class CompositeBackgroundDrawable(
     public val cssBackground: CSSBackgroundDrawable? = null,
 
     /** Background rendering Layer */
-    background: BackgroundDrawable? = null,
+    public val background: BackgroundDrawable? = null,
 
     /** Border rendering Layer */
-    border: BorderDrawable? = null,
+    public val border: BorderDrawable? = null,
 
     /** TouchableNativeFeeback set selection background, like "SelectableBackground" */
-    feedbackUnderlay: Drawable? = null,
+    public val feedbackUnderlay: Drawable? = null,
 
     /** Inset box-shadows */
-    innerShadows: LayerDrawable? = null,
+    public val innerShadows: List<Drawable> = emptyList(),
 
     /** Outline */
-    outline: OutlineDrawable? = null,
-) : LayerDrawable(emptyArray()) {
-  public var outerShadows: LayerDrawable? = outerShadows
-    private set
+    public val outline: OutlineDrawable? = null,
 
-  public var background: BackgroundDrawable? = background
-    private set
+    // Holder value for currently set insets
+    public var borderInsets: BorderInsets? = null,
 
-  public var border: BorderDrawable? = border
-    private set
-
-  public var feedbackUnderlay: Drawable? = feedbackUnderlay
-    private set
-
-  public var innerShadows: LayerDrawable? = innerShadows
-    private set
-
-  public var outline: OutlineDrawable? = outline
-    private set
-
-  // Holder value for currently set insets
-  public var borderInsets: BorderInsets? = null
-
-  // Holder value for currently set border radius
-  public var borderRadius: BorderRadiusStyle? = null
+    // Holder value for currently set border radius
+    public var borderRadius: BorderRadiusStyle? = null,
+) :
+    LayerDrawable(
+        createLayersArray(
+            originalBackground,
+            outerShadows,
+            cssBackground,
+            background,
+            border,
+            feedbackUnderlay,
+            innerShadows,
+            outline)) {
 
   init {
     // We want to overlay drawables, instead of placing future drawables within the content area of
     // previous ones. E.g. an EditText style may set padding on a TextInput, but we don't want to
     // constrain background color to the area inside of the padding.
     setPaddingMode(LayerDrawable.PADDING_MODE_STACK)
-
-    addLayer(originalBackground, ORIGINAL_BACKGROUND_ID)
-    addLayer(outerShadows, OUTER_SHADOWS_ID)
-    addLayer(cssBackground, CSS_BACKGROUND_ID)
-    addLayer(background, BACKGROUND_ID)
-    addLayer(border, BORDER_ID)
-    addLayer(feedbackUnderlay, FEEDBACK_UNDERLAY_ID)
-    addLayer(innerShadows, INNER_SHADOWS_ID)
-    addLayer(outline, OUTLINE_ID)
   }
 
   public fun withNewCssBackground(
       cssBackground: CSSBackgroundDrawable?
   ): CompositeBackgroundDrawable {
     return CompositeBackgroundDrawable(
-            context,
-            originalBackground,
-            outerShadows,
-            cssBackground,
-            background,
-            border,
-            feedbackUnderlay,
-            innerShadows,
-            outline)
-        .also { composite ->
-          composite.borderInsets = this.borderInsets
-          composite.borderRadius = this.borderRadius
-        }
+        context,
+        originalBackground,
+        outerShadows,
+        cssBackground,
+        background,
+        border,
+        feedbackUnderlay,
+        innerShadows,
+        outline,
+        borderInsets,
+        borderRadius,
+    )
   }
 
-  public fun withNewOuterShadow(outerShadows: LayerDrawable?): CompositeBackgroundDrawable =
-      withNewLayer(outerShadows, OUTER_SHADOWS_ID, this::outerShadows::set)
-
-  public fun withNewBackground(background: BackgroundDrawable?): CompositeBackgroundDrawable =
-      withNewLayer(background, BACKGROUND_ID, this::background::set)
-
-  public fun withNewBorder(border: BorderDrawable?): CompositeBackgroundDrawable =
-      withNewLayer(border, BORDER_ID, this::border::set)
-
-  public fun withNewFeedbackUnderlay(newUnderlay: Drawable?): CompositeBackgroundDrawable =
-      withNewLayer(newUnderlay, FEEDBACK_UNDERLAY_ID, this::feedbackUnderlay::set)
-
-  public fun withNewInnerShadow(innerShadows: LayerDrawable?): CompositeBackgroundDrawable =
-      withNewLayer(innerShadows, INNER_SHADOWS_ID, this::innerShadows::set)
-
-  public fun withNewOutline(outline: OutlineDrawable?): CompositeBackgroundDrawable =
-      withNewLayer(outline, OUTLINE_ID, this::outline::set)
-
-  /** @return true if the layer was updated, false if it was not */
-  private fun updateLayer(layer: Drawable?, id: Int): Boolean {
-    if (layer == null) {
-      return findDrawableByLayerId(id) == null
-    }
-
-    if (findDrawableByLayerId(id) == null) {
-      insertNewLayer(layer, id)
-    } else {
-      setDrawableByLayerId(id, layer)
-    }
-    invalidateSelf()
-    return true
+  public fun withNewBackground(background: BackgroundDrawable?): CompositeBackgroundDrawable {
+    return CompositeBackgroundDrawable(
+        context,
+        originalBackground,
+        outerShadows,
+        cssBackground,
+        background,
+        border,
+        feedbackUnderlay,
+        innerShadows,
+        outline,
+        borderInsets,
+        borderRadius,
+    )
   }
 
-  private fun insertNewLayer(layer: Drawable?, id: Int) {
-    layer ?: return
-
-    if (numberOfLayers == 0) {
-      addLayer(layer, id)
-      return
-    }
+  public fun withNewShadows(
+      outerShadows: List<Drawable>,
+      innerShadows: List<Drawable>
+  ): CompositeBackgroundDrawable {
+    return CompositeBackgroundDrawable(
+        context,
+        originalBackground,
+        outerShadows,
+        cssBackground,
+        background,
+        border,
+        feedbackUnderlay,
+        innerShadows,
+        outline,
+        borderInsets,
+        borderRadius,
+    )
+  }
 
-    for (i in 0..<numberOfLayers) {
-      if (id < getId(i)) {
-        val tempDrawable: Drawable = getDrawable(i)
-        val tempId = getId(i)
-        setDrawable(i, layer)
-        setId(i, id)
-        insertNewLayer(tempDrawable, tempId)
-        return
-      } else if (i == numberOfLayers - 1) {
-        addLayer(layer, id)
-        return
-      }
-    }
+  public fun withNewBorder(border: BorderDrawable): CompositeBackgroundDrawable {
+    return CompositeBackgroundDrawable(
+        context,
+        originalBackground,
+        outerShadows,
+        cssBackground,
+        background,
+        border,
+        feedbackUnderlay,
+        innerShadows,
+        outline,
+        borderInsets,
+        borderRadius,
+    )
   }
 
-  private fun addLayer(layer: Drawable?, id: Int) {
-    if (layer == null) {
-      return
-    }
+  public fun withNewOutline(outline: OutlineDrawable): CompositeBackgroundDrawable {
+    return CompositeBackgroundDrawable(
+        context,
+        originalBackground,
+        outerShadows,
+        cssBackground,
+        background,
+        border,
+        feedbackUnderlay,
+        innerShadows,
+        outline,
+        borderInsets,
+        borderRadius,
+    )
+  }
 
-    addLayer(layer)
-    layer.callback = this
-    setId(numberOfLayers - 1, id)
-    invalidateSelf()
+  public fun withNewFeedbackUnderlay(newUnderlay: Drawable?): CompositeBackgroundDrawable {
+    return CompositeBackgroundDrawable(
+        context,
+        originalBackground,
+        outerShadows,
+        cssBackground,
+        background,
+        border,
+        newUnderlay,
+        innerShadows,
+        outline,
+        borderInsets,
+        borderRadius,
+    )
   }
 
   /* Android's elevation implementation requires this to be implemented to know where to draw the
@@ -200,14 +199,14 @@ internal class CompositeBackgroundDrawable(
         pathForOutline.addRoundRect(
             RectF(bounds),
             floatArrayOf(
-                it.topLeft.horizontal + (computedBorderInsets?.left ?: 0f),
-                it.topLeft.vertical + (computedBorderInsets?.top ?: 0f),
-                it.topRight.horizontal + (computedBorderInsets?.right ?: 0f),
-                it.topRight.vertical + (computedBorderInsets?.top ?: 0f),
-                it.bottomRight.horizontal + (computedBorderInsets?.right ?: 0f),
-                it.bottomRight.vertical + (computedBorderInsets?.bottom ?: 0f),
-                it.bottomLeft.horizontal + (computedBorderInsets?.left ?: 0f),
-                it.bottomLeft.vertical) + (computedBorderInsets?.bottom ?: 0f),
+                (it.topLeft.horizontal + (computedBorderInsets?.left ?: 0f)).dpToPx(),
+                (it.topLeft.vertical + (computedBorderInsets?.top ?: 0f)).dpToPx(),
+                (it.topRight.horizontal + (computedBorderInsets?.right ?: 0f)).dpToPx(),
+                (it.topRight.vertical + (computedBorderInsets?.top ?: 0f)).dpToPx(),
+                (it.bottomRight.horizontal + (computedBorderInsets?.right ?: 0f)).dpToPx(),
+                (it.bottomRight.vertical + (computedBorderInsets?.bottom ?: 0f)).dpToPx(),
+                (it.bottomLeft.horizontal + (computedBorderInsets?.left ?: 0f)).dpToPx(),
+                (it.bottomLeft.vertical + (computedBorderInsets?.bottom ?: 0f)).dpToPx()),
             Path.Direction.CW)
       }
 
@@ -221,41 +220,27 @@ internal class CompositeBackgroundDrawable(
     }
   }
 
-  private fun <T> withNewLayer(
-      newLayer: T,
-      id: Int,
-      setNewLayer: (T) -> Unit,
-  ): CompositeBackgroundDrawable where T : Drawable? {
-    setNewLayer(newLayer)
-    if (ReactNativeFeatureFlags.enableNewBackgroundAndBorderDrawables()) {
-      if (updateLayer(newLayer, id)) {
-        return this
-      }
+  companion object {
+    private fun createLayersArray(
+        originalBackground: Drawable?,
+        outerShadows: List<Drawable>,
+        cssBackground: CSSBackgroundDrawable?,
+        background: BackgroundDrawable?,
+        border: BorderDrawable?,
+        feedbackUnderlay: Drawable?,
+        innerShadows: List<Drawable>,
+        outline: OutlineDrawable?
+    ): Array<Drawable?> {
+      val layers = mutableListOf<Drawable?>()
+      originalBackground?.let { layers.add(it) }
+      layers.addAll(outerShadows.asReversed())
+      cssBackground?.let { layers.add(it) }
+      background?.let { layers.add(it) }
+      border?.let { layers.add(it) }
+      feedbackUnderlay?.let { layers.add(it) }
+      layers.addAll(innerShadows.asReversed())
+      outline?.let { layers.add(it) }
+      return layers.toTypedArray()
     }
-    return CompositeBackgroundDrawable(
-            context,
-            originalBackground,
-            outerShadows,
-            cssBackground,
-            background,
-            border,
-            feedbackUnderlay,
-            innerShadows,
-            outline)
-        .also { composite ->
-          composite.borderInsets = this.borderInsets
-          composite.borderRadius = this.borderRadius
-        }
-  }
-
-  private companion object {
-    private const val ORIGINAL_BACKGROUND_ID: Int = 0
-    private const val OUTER_SHADOWS_ID: Int = 1
-    private const val CSS_BACKGROUND_ID: Int = 2
-    private const val BACKGROUND_ID: Int = 3
-    private const val BORDER_ID: Int = 4
-    private const val FEEDBACK_UNDERLAY_ID: Int = 5
-    private const val INNER_SHADOWS_ID: Int = 6
-    private const val OUTLINE_ID: Int = 7
   }
 }

@mihailapuste
Copy link

this patch works for me:

react-native+0.77.0.patch

Click me

Thanks, but unfortunately this wasn't it either.

@efstathiosntonas
Copy link

If it doesn't work then you guys are not building from source. There's no way the patch above is not working. I've been using it since the linked PR was merged.

@iM-GeeKy
Copy link

iM-GeeKy commented Feb 5, 2025

@efstathiosntonas I must be missing something still. I have other patches that apply fixes to native code and I've applied your patch and can see my node modules do in fact have your patch changes, but I'm still seeing the bizarre shadow behavior 🫠. When you build an Expo development client, it does build react-native from source to my understanding.

@efstathiosntonas
Copy link

@iM-GeeKy I have no clue how expo works but afaik it does not build react-native from source. Building from source takes way longer than normal builds.

@iM-GeeKy
Copy link

iM-GeeKy commented Feb 5, 2025

So I've been talking with the Expo devs and it turns out development clients do build RN from source for iOS but react native provides pre-builds for Android which is why the patch is not truly being applied. For those using Expo that need to apply the patch you can check out building from source.

@efstathiosntonas
Copy link

efstathiosntonas commented Feb 5, 2025

@iM-GeeKy thanks for confirming it. Just a side note, if someone plans to build from source do not use @nightly (as stated on the link shared above) but @0.77.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Issue: Author Provided Repro This issue can be reproduced in Snack or an attached project. Platform: Android Android applications. Resolution: Fixed A PR that fixes this issue has been merged.
Projects
None yet
Development

No branches or pull requests