mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Introduce "BoxShadowDrawable" for Android box shadows (#43722)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/43722 This change adds a drawable, when when drawn on the bounds of a border-box sized view, will draw a spec compliant box-shadow outside the box. This is reliant on Android `RenderNode` and `RenderEffect` APIs provided by API 31. Inset box shadows can also be added using a similar method, but this is not done yet. The code which manages this is in flux, but the underlying drawable should be good. Will add some tests once it's more wired up. Changelog: [Internal] Reviewed By: javache, cortinico Differential Revision: D55561465 fbshipit-source-id: 6180568cff2779b826e73bb9184dbe042863b262
This commit is contained in:
committed by
Facebook GitHub Bot
parent
c710386e72
commit
314a919df6
+96
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.uimanager.drawable
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RenderNode
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.facebook.common.logging.FLog
|
||||
import com.facebook.react.uimanager.FilterHelper
|
||||
import com.facebook.react.uimanager.PixelUtil
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
private const val TAG = "BoxShadowDrawable"
|
||||
|
||||
// "the resulting shadow must approximate {...} a Gaussian blur with a standard deviation equal
|
||||
// to half the blur radius"
|
||||
// https://www.w3.org/TR/css-backgrounds-3/#shadow-blur
|
||||
private const val BLUR_RADIUS_SIGMA_SCALE = 0.5f
|
||||
|
||||
/** Draws an outer-box shadow https://www.w3.org/TR/css-backgrounds-3/#shadow-shape */
|
||||
@RequiresApi(31)
|
||||
internal class BoxShadowDrawable(
|
||||
context: Context,
|
||||
private val background: CSSBackgroundDrawable,
|
||||
shadowColor: Int,
|
||||
private val offsetX: Float,
|
||||
private val offsetY: Float,
|
||||
blurRadius: Float,
|
||||
private val spread: Float,
|
||||
) : Drawable() {
|
||||
|
||||
private val shadowShapeDrawable = CSSBackgroundDrawable(context).apply { color = shadowColor }
|
||||
|
||||
private val renderNode =
|
||||
RenderNode("BoxShadowDrawable").apply {
|
||||
clipToBounds = false
|
||||
setRenderEffect(FilterHelper.createBlurEffect(blurRadius * BLUR_RADIUS_SIGMA_SCALE))
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
if (!canvas.isHardwareAccelerated) {
|
||||
FLog.w(TAG, "BoxShadowDrawable requires a hardware accelerated canvas")
|
||||
return
|
||||
}
|
||||
|
||||
val spreadExtent = PixelUtil.toPixelFromDIP(spread).roundToInt().coerceAtLeast(0)
|
||||
val shadowShapeFrame = Rect(bounds).apply { inset(-spreadExtent, -spreadExtent) }
|
||||
val shadowShapeBounds = Rect(0, 0, shadowShapeFrame.width(), shadowShapeFrame.height())
|
||||
|
||||
if (shadowShapeDrawable.bounds != shadowShapeBounds ||
|
||||
shadowShapeDrawable.layoutDirection != layoutDirection ||
|
||||
shadowShapeDrawable.borderRadius != background.borderRadius ||
|
||||
shadowShapeDrawable.colorFilter != colorFilter) {
|
||||
shadowShapeDrawable.bounds = shadowShapeBounds
|
||||
shadowShapeDrawable.layoutDirection = layoutDirection
|
||||
shadowShapeDrawable.borderRadius = background.borderRadius
|
||||
shadowShapeDrawable.colorFilter = colorFilter
|
||||
|
||||
with(renderNode) {
|
||||
setPosition(
|
||||
Rect(shadowShapeFrame).apply {
|
||||
offset(
|
||||
PixelUtil.toPixelFromDIP(offsetX).roundToInt(),
|
||||
PixelUtil.toPixelFromDIP(offsetY).roundToInt())
|
||||
})
|
||||
|
||||
beginRecording().let { canvas ->
|
||||
shadowShapeDrawable.draw(canvas)
|
||||
endRecording()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
with(canvas) {
|
||||
clipOutPath(background.borderBoxPath())
|
||||
drawRenderNode(renderNode)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setAlpha(alpha: Int) {
|
||||
renderNode.alpha = alpha / 255f
|
||||
}
|
||||
|
||||
override fun setColorFilter(colorFilter: ColorFilter?): Unit = Unit
|
||||
|
||||
override fun getOpacity(): Int = (renderNode.alpha * 255).roundToInt()
|
||||
}
|
||||
Reference in New Issue
Block a user