Skip to content

Commit 2c208f8

Browse files
fabriziocuccimeta-codesync[bot]
authored andcommitted
Override getClipBounds to expose overflow clipping to the framework (#56606)
Summary: Pull Request resolved: #56606 Override `getClipBounds()` on ReactViewGroup to return the padding box rect when `overflow` is hidden or scroll, gated behind the `syncAndroidClipBoundsWithOverflow` feature flag. This exposes the view's clipping behavior to the Android framework via the standard `View.getClipBounds()` API: - `getClipBounds(Rect): Boolean` returns `true` and populates the rect with the padding box when overflow is hidden/scroll, or `false` when overflow is visible - `getClipBounds(): Rect?` returns the padding box rect or `null` respectively This allows framework-level systems to inspect a view and determine whether it clips its children by checking `getClipBounds() != null`, and to obtain the actual clipping region for `getGlobalVisibleRect()` calculations. ## Why not `setClipBounds()`? An alternative approach would be to call `View.setClipBounds(Rect)` which sets the `mClipBounds` field directly. This has the advantage of being picked up by AOSP's `ViewGroup.getChildVisibleRect()` (which reads `mClipBounds` via direct field access rather than calling `getClipBounds()`). However, `setClipBounds()` also propagates the clip rect to the RenderNode via `mRenderNode.setClipRect()`, which clips the view's own rendering — including its borders. It also requires maintaining a Rect that must be kept in sync across size changes, border width changes, and overflow changes. For now, the `getClipBounds()` override is sufficient because harvesting systems query the method rather than the field. If we find that AOSP's field-based path is also needed, we can switch to `setClipBounds()` in the future. Adds `BackgroundStyleApplicator.getPaddingBoxRect()` (internal via buck friend_paths) which computes the padding box (view bounds minus border insets) reusing the same border inset resolution logic as `clipToPaddingBox()`. Changelog: [Internal] Reviewed By: twasilczyk, javache Differential Revision: D102353840 fbshipit-source-id: fe1d52e3c000ba1b5e2d21eac75928be24a86645
1 parent 497d9c1 commit 2c208f8

3 files changed

Lines changed: 65 additions & 0 deletions

File tree

packages/react-native/ReactAndroid/api/ReactAndroid.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6457,6 +6457,8 @@ public class com/facebook/react/views/view/ReactViewGroup : android/view/ViewGro
64576457
protected fun drawChild (Landroid/graphics/Canvas;Landroid/view/View;J)Z
64586458
public fun endViewTransition (Landroid/view/View;)V
64596459
public final fun getAxOrderList ()Ljava/util/List;
6460+
public fun getClipBounds ()Landroid/graphics/Rect;
6461+
public fun getClipBounds (Landroid/graphics/Rect;)Z
64606462
public fun getClippingRect (Landroid/graphics/Rect;)V
64616463
public fun getHitSlopRect ()Landroid/graphics/Rect;
64626464
public fun getOverflow ()Ljava/lang/String;

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,35 @@ public object BackgroundStyleApplicator {
465465
clipToPaddingBoxWithAntiAliasing(view, canvas, null)
466466
}
467467

468+
/**
469+
* Populates [outRect] with the padding box rect of the view.
470+
*
471+
* The padding box is the area within the borders of the view. For views without a
472+
* [CompositeBackgroundDrawable] or without borders, this returns the full view bounds.
473+
*
474+
* This is useful for overriding [View.getClipBounds] to communicate the view's clipping region to
475+
* the Android framework (e.g. for [View.getGlobalVisibleRect] calculations).
476+
*
477+
* @param view The view whose padding box to compute
478+
* @param outRect The rect to populate with the padding box bounds
479+
*/
480+
internal fun getPaddingBoxRect(view: View, outRect: Rect) {
481+
val composite = getCompositeBackgroundDrawable(view)
482+
val computedBorderInsets =
483+
composite?.borderInsets?.resolve(composite.layoutDirection, view.context)
484+
if (computedBorderInsets == null) {
485+
outRect.set(0, 0, view.width, view.height)
486+
return
487+
}
488+
489+
val left = (computedBorderInsets.left.dpToPx()).toInt()
490+
val top = (computedBorderInsets.top.dpToPx()).toInt()
491+
val right = (view.width.toFloat() - computedBorderInsets.right.dpToPx()).toInt()
492+
val bottom = (view.height.toFloat() - computedBorderInsets.bottom.dpToPx()).toInt()
493+
494+
outRect.set(left, top, right, bottom)
495+
}
496+
468497
/**
469498
* Clips the canvas to the padding box of the view.
470499
*

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import com.facebook.react.touch.OnInterceptTouchEventListener
3737
import com.facebook.react.touch.ReactHitSlopView
3838
import com.facebook.react.touch.ReactInterceptingViewGroup
3939
import com.facebook.react.uimanager.BackgroundStyleApplicator.clipToPaddingBox
40+
import com.facebook.react.uimanager.BackgroundStyleApplicator.getPaddingBoxRect
4041
import com.facebook.react.uimanager.BackgroundStyleApplicator.setBackgroundColor
4142
import com.facebook.react.uimanager.BackgroundStyleApplicator.setBorderColor
4243
import com.facebook.react.uimanager.BackgroundStyleApplicator.setBorderRadius
@@ -820,6 +821,39 @@ public open class ReactViewGroup public constructor(context: Context?) :
820821
invalidate()
821822
}
822823

824+
/**
825+
* Returns the clip bounds for this view based on the overflow property.
826+
*
827+
* When overflow is hidden or scroll, returns the padding box rect (the area inside the borders)
828+
* so that systems querying [View.getClipBounds] can determine the view's clipping region. Returns
829+
* null when overflow is visible (no clipping).
830+
*/
831+
override fun getClipBounds(): Rect? {
832+
if (
833+
ReactNativeFeatureFlags.syncAndroidClipBoundsWithOverflow() &&
834+
_overflow != null &&
835+
_overflow != Overflow.VISIBLE
836+
) {
837+
val rect = Rect()
838+
getPaddingBoxRect(this, rect)
839+
return rect
840+
}
841+
return super.getClipBounds()
842+
}
843+
844+
/** See [getClipBounds]. */
845+
override fun getClipBounds(outRect: Rect): Boolean {
846+
if (
847+
ReactNativeFeatureFlags.syncAndroidClipBoundsWithOverflow() &&
848+
_overflow != null &&
849+
_overflow != Overflow.VISIBLE
850+
) {
851+
getPaddingBoxRect(this, outRect)
852+
return true
853+
}
854+
return super.getClipBounds(outRect)
855+
}
856+
823857
override fun setOverflowInset(left: Int, top: Int, right: Int, bottom: Int) {
824858
if (
825859
needsIsolatedLayer(this) &&

0 commit comments

Comments
 (0)