-
Notifications
You must be signed in to change notification settings - Fork 24.5k
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
Comments
same here! |
@joevilches @NickGerleman is this related to recent changes to |
This implies background drawable outline is incorrect. @jorge-cab has been working around here recently, and I can ask. |
@NickGerleman hi, is there any update? Thank you |
@efstathiosntonas getting a fix up now |
Fixed by #48982 |
What to do in my existing project to get this patch. |
@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) |
Pick request for next 0.77 patch: reactwg/react-native-releases#761 |
@NickGerleman I'm using the following
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. |
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. |
@NickGerleman I also just tried this patch and didnt work for me either. |
this patch works for me:
Click mediff --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
}
} |
Thanks, but unfortunately this wasn't it either. |
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. |
@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. |
@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. |
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. |
Description
When the
elevation
prop is applied to aView
or elements likeTouchableOpacity
, a shadow is expected. However, since version 0.77, the shadow appears distorted, as if theborderRadius
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 theborderRadius
should normally be 25 for an oval shape, it now needs to be adjusted to 75 to look correct.Steps to reproduce
React Native Version
0.77.0
Affected Platforms
Runtime - Android
Output of
npx react-native info
Stacktrace or Logs
Reproducer
https://github.com/hrastnik/rn-77-border-radius-elevation-repro
Screenshots and Videos
Screenshot 1 - code:
Screenshot 2 - output
The text was updated successfully, but these errors were encountered: