Repository navigation
Conversation
Before Android 11, HWUI's Canvas.clipPath is not anti-aliased, so a view with overflow: hidden and a borderRadius has stepped corners on Android 9 and 10. There, clip with the view's outline when the padding box is a round rect with one circular radius and no border, and nothing is drawn outside the bounds for the outline to cut off. HWUI clips outlines with an anti-aliased round rect and no offscreen layer. ReactImageView takes the same path when it can. Every other padding box keeps the canvas clip: masking it with a layer cost 99% janky frames for a screen of bordered cards on Android 9. BackgroundStyleApplicator.updatePaddingBoxClip is the new per-draw entry point for RN's own views; clipToPaddingBox keeps its stateless contract. getOutline resolves percentage radii against the bounds in dp, as the padding box does. Assisted-by: Claude-Code:claude-opus-5-5
knappam
marked this pull request as ready for review
October 28, 2026 18:26
ReactViewGroup.setBorderRadius(Float) is deprecated, which fails the unit test compile under -PenableWarningsAsErrors=true. Assisted-by: Claude-Code:claude-opus-5-5
The tests ran in Robolectric SDKs 27-30, which nothing else in the suite uses. Each SDK loads its own android-all runtime into the test JVM, and build_android ran out of memory in Robolectric and hung until the job timed out. The clip choice depends only on Build.VERSION.SDK_INT, so the tests now set it instead. Assisted-by: Claude-Code:claude-opus-5-5
Visual regressionNo visual differences at ios: no capture |
|
@Abbondanzo has imported this pull request. If you are a Meta employee, you can view this in D128394242. |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
On Android 9 and 10, a view with
overflow: 'hidden'and aborderRadiushas stepped, aliased corners. RN clips the padding box withCanvas.clipPath, and HWUI'sSkiaCanvas::clipPathdoes not request anti-aliasing before Android 11 (AOSP frameworks/basee431e316981e, "Added support for antialiased canvas clipping", first inandroid-11.0.0_r1;android-9.0.0_r1andandroid-10.0.0_r1callmCanvas->clipPath(*path, op)without it). It is most visible where a dark photo meets a light card.#54525 fixed this for
Imageon API 28 and below by drawing into a layer and masking it with an anti-aliased path. Clipping views (View, scroll views, text) still use the hard clip, and API 29 is missed by the<= Pgate.This change, on Android 9 and 10 only, clips with the view's outline (
clipToOutline) when the padding box is a round rect with one circular radius and no border, and nothing is drawn outside the bounds that the outline would cut off (box shadows, outline, a feedback ripple).RenderNodeDrawableclips outlines with an anti-aliasedclipRRecton every version, and the outline clip draws no offscreen layer. This is the common case: cards, tiles, avatars.ReactImageViewtakes the same path when it can, so a rounded image there no longer needs #54525's layer on API 28.Every other rounded padding box (a border, mixed corner radii) keeps today's clip. Masking it with #54525's layer and anti-aliased path was measured and rejected: on the Android 9 device below, a screen of 60 animated rounded cards with a 2 dp border went from 2.9% janky frames to 99.3% (RenderThread issue-draw ~4 ms → ~15 ms per frame), while the outline clip went from 0.97% to 0.18% for the same cards without a border:
clipPathtoday, no borderclipPathtoday, 2 dp borderBackgroundStyleApplicator.updatePaddingBoxClip(view, canvas, clip)is the new per-draw entry point for RN's own views (ReactViewGroup, the scroll views,ReactTextView,PreparedLayoutTextView,ReactEditText), andupdatePaddingBoxOutlineClipthe outline part alone (used byReactImageView, which keeps #54525's mask when the outline cannot apply).clipfalse removes an outline clip set by an earlier call, so overflow can change back to visible.clipToPaddingBoxkeeps its stateless contract for third-party callers.Android 8 and earlier are unchanged: their OpenGL renderer replaces, rather than intersects, an inherited round-rect outline clip (
Snapshot::setClippingRoundRect), so nested rounded clips would escape their parent. Android 11 and later already anti-aliasclipPathand are unchanged.CompositeBackgroundDrawable.getOutlinenow resolves percentage radii against the bounds in dp, ascreatePaddingBoxPathalready does, so the outline matches the padding box it clips. Previously it resolved them against the bounds in px, so a percentage radius's shadow outline was too large by the screen density.Changelog:
[ANDROID] [FIXED] - Anti-aliased rounded
overflow: hiddenclips on Android 9 and 10[ANDROID] [FIXED] - Elevation shadow outline for percentage
borderRadiusat densities other than 1[ANDROID] [ADDED] -
BackgroundStyleApplicator.updatePaddingBoxClipandupdatePaddingBoxOutlineClipTest Plan:
Unit tests:
./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest --tests "com.facebook.react.uimanager.*" --tests "com.facebook.react.views.*"passes (477 tests in 60 suites).BackgroundStyleApplicatorClipTest(14 tests, Robolectric SDK 28 at xhdpi, plus 27, 29, 30 and 33) covers:updatePaddingBoxOutlineClipreporting whether it clipped, without clipping the canvas;clipToOutlineset by someone else;clipfalse;ReactViewGroupswitching with overflow;On device: an Android 9 (API 28) device at density 1.33, with a test app showing dark content in light rounded
overflow: hiddenviews at four radii, and the same shapes drawn as a rounded background (anti-aliased paint) as the reference. Corner zoom ×8, before | after | reference:Partly covered edge pixels per corner (radii 2/7/14/23 dp), before → after: clipped
View0/0/0/0 → 2/11/20/32, clippedImage0/0/0/0 → 4/11/21/32; the background-fill reference is 2/11/19/32. An Android 11 device renders pixel-identically before and after. The frame-time table above comes from the same device: 60 cards of 216×150 dp with an 18 dp radius, each with a photo and a native-driver animation, scrolled byinput swipefor 20 s per run, cases interleaved over 5 repetitions, read fromdumpsys gfxinfo.The device runs were made with this change applied to 0.81.5 and to 0.83.9, which share this code path. On
main, only the unit tests have run. Not yet covered: an Android 10 device.