Skip to content

Commit d56d9d9

Browse files
NickGerlemanfacebook-github-bot
authored andcommitted
Decouple CSSBackgroundDrawable from OutsetBoxShadowDrawable (#45805)
Summary: Pull Request resolved: #45805 After recent changes where we dive into paths ourselves, we really don't have a good reason to use the heavy CSSBackgroundDrawable. Accept a box shadow style in place of a reference to the original drawable, and then draw using calculated round rect path instead of new whole Drawable. This lets us avoid a lot of conversions as well (with the last diff already removing some). This should also resolve a crash we started seeing: ``` androidx.core.util.Preconditions.checkNotNull (Preconditions.java:136) [inlined] - com.facebook.react.uimanager.drawable.CSSBackgroundDrawable.drawRoundedBackgroundWithBorders (CSSBackgroundDrawable.java:386) [inlined] - com.facebook.react.uimanager.drawable.CSSBackgroundDrawable.draw (CSSBackgroundDrawable.java:142) - com.facebook.react.uimanager.drawable.OutsetBoxShadowDrawable.draw (OutsetBoxShadowDrawable.kt:137) - android.graphics.drawable.LayerDrawable.draw (LayerDrawable.java:1019) ``` Changelog: [Internal] Reviewed By: joevilches Differential Revision: D60401423 fbshipit-source-id: 693d9bf5e85956290db932cdb18f15ba26446894
1 parent bbd5b5e commit d56d9d9

File tree

2 files changed

+74
-42
lines changed

2 files changed

+74
-42
lines changed

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BackgroundStyleApplicator.kt

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.facebook.react.uimanager.drawable.CompositeBackgroundDrawable
2323
import com.facebook.react.uimanager.drawable.InsetBoxShadowDrawable
2424
import com.facebook.react.uimanager.drawable.OutsetBoxShadowDrawable
2525
import com.facebook.react.uimanager.style.BorderRadiusProp
26+
import com.facebook.react.uimanager.style.BorderRadiusStyle
2627
import com.facebook.react.uimanager.style.BorderStyle
2728
import com.facebook.react.uimanager.style.BoxShadow
2829
import com.facebook.react.uimanager.style.LogicalEdge
@@ -75,7 +76,17 @@ public object BackgroundStyleApplicator {
7576
corner: BorderRadiusProp,
7677
// TODO: LengthPercentage silently converts from pixels to DIPs before here already
7778
radius: LengthPercentage?
78-
): Unit = ensureCSSBackground(view).setBorderRadius(corner, radius)
79+
): Unit {
80+
ensureCSSBackground(view).setBorderRadius(corner, radius)
81+
82+
if (Build.VERSION.SDK_INT >= 31) {
83+
getCompositeBackgroundDrawable(view)?.outerShadows?.forEach { shadow ->
84+
val outsetShadow = shadow as OutsetBoxShadowDrawable
85+
outsetShadow.borderRadius = outsetShadow.borderRadius ?: BorderRadiusStyle()
86+
outsetShadow.borderRadius?.set(corner, radius)
87+
}
88+
}
89+
}
7990

8091
@JvmStatic
8192
public fun getBorderRadius(view: View, corner: BorderRadiusProp): LengthPercentage? =
@@ -117,7 +128,7 @@ public object BackgroundStyleApplicator {
117128
outerShadows.add(
118129
OutsetBoxShadowDrawable(
119130
context = view.context,
120-
background = ensureCSSBackground(view),
131+
borderRadius = ensureCSSBackground(view).borderRadius,
121132
shadowColor = color,
122133
offsetX = offsetX,
123134
offsetY = offsetY,
@@ -195,6 +206,9 @@ public object BackgroundStyleApplicator {
195206
return compositeDrawable
196207
}
197208

209+
private fun getCompositeBackgroundDrawable(view: View): CompositeBackgroundDrawable? =
210+
view.background as? CompositeBackgroundDrawable
211+
198212
private fun ensureCSSBackground(view: View): CSSBackgroundDrawable {
199213
val compositeBackgroundDrawable = ensureCompositeBackgroundDrawable(view)
200214
if (compositeBackgroundDrawable.cssBackground != null) {
@@ -206,10 +220,6 @@ public object BackgroundStyleApplicator {
206220
}
207221
}
208222

209-
private fun getCSSBackground(view: View): CSSBackgroundDrawable? {
210-
if (view.background is CompositeBackgroundDrawable) {
211-
return (view.background as CompositeBackgroundDrawable).cssBackground
212-
}
213-
return null
214-
}
223+
private fun getCSSBackground(view: View): CSSBackgroundDrawable? =
224+
getCompositeBackgroundDrawable(view)?.cssBackground
215225
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/OutsetBoxShadowDrawable.kt

Lines changed: 56 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package com.facebook.react.uimanager.drawable
1010
import android.content.Context
1111
import android.graphics.Canvas
1212
import android.graphics.ColorFilter
13+
import android.graphics.Paint
1314
import android.graphics.Path
1415
import android.graphics.Rect
1516
import android.graphics.RectF
@@ -19,8 +20,6 @@ import androidx.annotation.RequiresApi
1920
import com.facebook.common.logging.FLog
2021
import com.facebook.react.common.annotations.UnstableReactNativeAPI
2122
import com.facebook.react.uimanager.FilterHelper
22-
import com.facebook.react.uimanager.LengthPercentage
23-
import com.facebook.react.uimanager.LengthPercentageType
2423
import com.facebook.react.uimanager.PixelUtil
2524
import com.facebook.react.uimanager.style.BorderRadiusStyle
2625
import com.facebook.react.uimanager.style.ComputedBorderRadius
@@ -38,14 +37,20 @@ private const val BLUR_RADIUS_SIGMA_SCALE = 0.5f
3837
@OptIn(UnstableReactNativeAPI::class)
3938
internal class OutsetBoxShadowDrawable(
4039
private val context: Context,
41-
private val background: CSSBackgroundDrawable,
42-
shadowColor: Int,
40+
borderRadius: BorderRadiusStyle? = null,
41+
private val shadowColor: Int,
4342
private val offsetX: Float,
4443
private val offsetY: Float,
4544
private val blurRadius: Float,
4645
private val spread: Float,
4746
) : Drawable() {
48-
private val shadowShapeDrawable = CSSBackgroundDrawable(context).apply { color = shadowColor }
47+
public var borderRadius = borderRadius
48+
set(value) {
49+
if (value != field) {
50+
field = value
51+
invalidateSelf()
52+
}
53+
}
4954

5055
private val renderNode =
5156
RenderNode(TAG).apply {
@@ -54,12 +59,18 @@ internal class OutsetBoxShadowDrawable(
5459
}
5560

5661
private val shadowClipOutPath: Path = Path()
62+
private val shadowOuterPath: Path = Path()
63+
private val shadowOuterRect: RectF = RectF()
64+
private val shadowPaint = Paint().apply { color = shadowColor }
65+
66+
private var lastBounds: Rect? = null
67+
private var lastBorderRadius: ComputedBorderRadius? = null
5768

5869
override fun setAlpha(alpha: Int) {
5970
renderNode.alpha = alpha / 255f
6071
}
6172

62-
override fun setColorFilter(colorFilter: ColorFilter?): Unit = Unit
73+
override fun setColorFilter(colorFilter: ColorFilter?) = Unit
6374

6475
override fun getOpacity(): Int = (renderNode.alpha * 255).roundToInt()
6576

@@ -76,40 +87,33 @@ internal class OutsetBoxShadowDrawable(
7687
val resolutionWidth = bounds.width().toFloat()
7788
val resolutionHeight = bounds.height().toFloat()
7889
val computedBorderRadii =
79-
background.borderRadius.resolve(layoutDirection, context, resolutionWidth, resolutionHeight)
90+
borderRadius?.resolve(layoutDirection, context, resolutionWidth, resolutionHeight)
8091
val shadowBorderRadii =
81-
ComputedBorderRadius(
82-
topLeft = adjustRadiusForSpread(computedBorderRadii.topLeft, spreadExtent.toFloat()),
83-
topRight = adjustRadiusForSpread(computedBorderRadii.topRight, spreadExtent.toFloat()),
84-
bottomRight =
85-
adjustRadiusForSpread(computedBorderRadii.bottomRight, spreadExtent.toFloat()),
86-
bottomLeft =
87-
adjustRadiusForSpread(computedBorderRadii.bottomLeft, spreadExtent.toFloat()),
88-
)
92+
computedBorderRadii?.let { radii ->
93+
ComputedBorderRadius(
94+
topLeft = adjustRadiusForSpread(radii.topLeft, spreadExtent.toFloat()),
95+
topRight = adjustRadiusForSpread(radii.topRight, spreadExtent.toFloat()),
96+
bottomRight = adjustRadiusForSpread(radii.bottomRight, spreadExtent.toFloat()),
97+
bottomLeft = adjustRadiusForSpread(radii.bottomLeft, spreadExtent.toFloat()),
98+
)
99+
}
89100

90101
if (!renderNode.hasDisplayList() ||
91-
shadowShapeDrawable.bounds != shadowShapeBounds ||
92-
shadowShapeDrawable.layoutDirection != layoutDirection ||
93-
shadowShapeDrawable.computedBorderRadius != shadowBorderRadii ||
94-
shadowShapeDrawable.colorFilter != colorFilter) {
95-
shadowShapeDrawable.bounds = shadowShapeBounds
96-
shadowShapeDrawable.layoutDirection = layoutDirection
97-
shadowShapeDrawable.borderRadius =
98-
BorderRadiusStyle(
99-
topLeft = LengthPercentage(shadowBorderRadii.topLeft, LengthPercentageType.POINT),
100-
topRight = LengthPercentage(shadowBorderRadii.topRight, LengthPercentageType.POINT),
101-
bottomLeft =
102-
LengthPercentage(shadowBorderRadii.bottomLeft, LengthPercentageType.POINT),
103-
bottomRight =
104-
LengthPercentage(shadowBorderRadii.bottomRight, LengthPercentageType.POINT))
105-
shadowShapeDrawable.colorFilter = colorFilter
102+
lastBounds != shadowShapeBounds ||
103+
lastBorderRadius != shadowBorderRadii) {
104+
lastBounds = shadowShapeBounds
105+
lastBorderRadius = shadowBorderRadii
106+
shadowOuterRect.set(
107+
RectF(bounds).apply { inset(-spreadExtent.toFloat(), -spreadExtent.toFloat()) })
106108

107109
// We remove the portion of the shadow which overlaps the background border box, to avoid
108110
// showing the shadow shape e.g. behind a transparent background. There may be a subpixel gap
109111
// between the border box path, and the edge of border rendering, so we slightly inflate the
110112
// shadow to cover it, placing it below the borders.
111-
shadowClipOutPath.rewind()
112-
if (background.hasRoundedBorders()) {
113+
if (computedBorderRadii != null &&
114+
shadowBorderRadii != null &&
115+
computedBorderRadii.hasRoundedBorders()) {
116+
shadowClipOutPath.rewind()
113117
val subpixelInsetBounds = RectF(bounds).apply { inset(0.4f, 0.4f) }
114118
shadowClipOutPath.addRoundRect(
115119
subpixelInsetBounds,
@@ -123,6 +127,20 @@ internal class OutsetBoxShadowDrawable(
123127
computedBorderRadii.bottomLeft,
124128
computedBorderRadii.bottomLeft),
125129
Path.Direction.CW)
130+
131+
shadowOuterPath.rewind()
132+
shadowOuterPath.addRoundRect(
133+
shadowOuterRect,
134+
floatArrayOf(
135+
shadowBorderRadii.topLeft,
136+
shadowBorderRadii.topLeft,
137+
shadowBorderRadii.topRight,
138+
shadowBorderRadii.topRight,
139+
shadowBorderRadii.bottomRight,
140+
shadowBorderRadii.bottomRight,
141+
shadowBorderRadii.bottomLeft,
142+
shadowBorderRadii.bottomLeft),
143+
Path.Direction.CW)
126144
}
127145

128146
with(renderNode) {
@@ -134,7 +152,11 @@ internal class OutsetBoxShadowDrawable(
134152
})
135153

136154
beginRecording().let { renderNodeCanvas ->
137-
shadowShapeDrawable.draw(renderNodeCanvas)
155+
if (shadowBorderRadii?.hasRoundedBorders() == true) {
156+
renderNodeCanvas.drawPath(shadowOuterPath, shadowPaint)
157+
} else {
158+
renderNodeCanvas.drawRect(shadowOuterRect, shadowPaint)
159+
}
138160
endRecording()
139161
}
140162
}
@@ -143,7 +165,7 @@ internal class OutsetBoxShadowDrawable(
143165
with(canvas) {
144166
save()
145167

146-
if (background.hasRoundedBorders()) {
168+
if (computedBorderRadii?.hasRoundedBorders() == true) {
147169
clipOutPath(shadowClipOutPath)
148170
} else {
149171
clipOutRect(bounds)

0 commit comments

Comments
 (0)