Skip to content

Clip ripple masks to match border radius on Android (#58945) - #58945

Open
jasozh wants to merge 2 commits into
react:mainfrom
jasozh:export-D123816883
Open

jasozh wants to merge 2 commits into
react:mainfrom
jasozh:export-D123816883

Conversation

@jasozh

@jasozh jasozh commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary:

Fixes #34553, where the ripple effect is a square even if the underlying Pressable is rounded.

Root Cause

For bounded ripples, ReactDrawableHelper creates a RippleDrawable with a ColorDrawable mask. This mask is rectangular and remains that way when added to CompositeBackgroundDrawable as a feedback layer, even if a border radius is configured.

This PR

Modify ReactDrawableHelper to replace the RippleDrawable's rectangular ColorDrawable mask with a BackgroundDrawable. If border radius is set, getOpacity() returns PixelFormat.TRANSLUCENT which makes RippleDrawable draw the mask into an alpha-only bitmap so the ripple respects the border radius. Otherwise, it returns PixelFormat.OPAQUE to avoid the cost of creating the bitmap and match the original ColorDrawable behavior.

Modify BackgroundStyleApplicator to update the ripple mask's border radius during setFeedbackUnderlay(), setFeedbackOverlay(), and setBorderRadius(). Also make ReactViewGroup.applyNativeForeground() call setFeedbackOverlay() so that foreground ripples are properly clipped.

Changelog: [Android][Fixed] - Fix Pressable ripple leaking past rounded corners

Differential Revision: D123816883

@meta-codesync

meta-codesync Bot commented Oct 8, 2026

Copy link
Copy Markdown

@jasozh has exported this pull request. If you are a Meta employee, you can view the originating Diff in D123816883.

@facebook-github-tools facebook-github-tools Bot added p: Facebook Partner: Facebook Partner labels Oct 8, 2026
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 8, 2026
@meta-codesync meta-codesync Bot changed the title Clip ripple masks to match border radius on Android Clip ripple masks to match border radius on Android (#58945) Oct 12, 2026
jasozh added a commit to jasozh/react-native that referenced this pull request Oct 12, 2026
Summary:

Fixes [react#34553](react#34553), where the ripple effect is a square even if the underlying Pressable is rounded.

### Root Cause

For bounded ripples, ReactDrawableHelper creates a RippleDrawable with a ColorDrawable mask. This mask is rectangular and remains that way when added to CompositeBackgroundDrawable as a feedback layer, even if a border radius is configured.

### This PR

Modify ReactDrawableHelper to replace the RippleDrawable's rectangular ColorDrawable mask with a BackgroundDrawable. If border radius is set, `getOpacity()` returns `PixelFormat.TRANSLUCENT` which makes RippleDrawable draw the mask into an alpha-only bitmap so the ripple respects the border radius. Otherwise, it returns `PixelFormat.OPAQUE` to avoid the cost of creating the bitmap and match the original ColorDrawable behavior.

Modify BackgroundStyleApplicator to update the ripple mask's border radius during `setFeedbackUnderlay()`, `setFeedbackOverlay()`, and `setBorderRadius()`. Also make `ReactViewGroup.applyNativeForeground()` call `setFeedbackOverlay()` so that foreground ripples are properly clipped.

Changelog: [Android][Fixed] - Fix Pressable ripple leaking past rounded corners

Differential Revision: D123816883
@jasozh
jasozh force-pushed the export-D123816883 branch from 01dd796 to 8ea2c7a Compare October 12, 2026 22:19
jasozh added a commit to jasozh/react-native that referenced this pull request Oct 12, 2026
Summary:
Pull Request resolved: react#58945

Fixes [react#34553](react#34553), where the ripple effect is a square even if the underlying Pressable is rounded.

### Root Cause

For bounded ripples, ReactDrawableHelper creates a RippleDrawable with a ColorDrawable mask. This mask is rectangular and remains that way when added to CompositeBackgroundDrawable as a feedback layer, even if a border radius is configured.

### This PR

Modify ReactDrawableHelper to replace the RippleDrawable's rectangular ColorDrawable mask with a BackgroundDrawable. If border radius is set, `getOpacity()` returns `PixelFormat.TRANSLUCENT` which makes RippleDrawable draw the mask into an alpha-only bitmap so the ripple respects the border radius. Otherwise, it returns `PixelFormat.OPAQUE` to avoid the cost of creating the bitmap and match the original ColorDrawable behavior.

Modify BackgroundStyleApplicator to update the ripple mask's border radius during `setFeedbackUnderlay()`, `setFeedbackOverlay()`, and `setBorderRadius()`. Also make `ReactViewGroup.applyNativeForeground()` call `setFeedbackOverlay()` so that foreground ripples are properly clipped.

Changelog: [Android][Fixed] - Fix Pressable ripple leaking past rounded corners

Differential Revision: D123816883
@jasozh
jasozh force-pushed the export-D123816883 branch from 8ea2c7a to c6dd234 Compare October 12, 2026 22:22
Summary:
Add a new RNTester example in PressableExample that reproduces the issue where ripple effects don't clip to the Pressable's border radius.

Changelog: [Internal]

Differential Revision: D123816852
@jasozh
jasozh force-pushed the export-D123816883 branch from c6dd234 to e9a4572 Compare October 21, 2026 22:44
jasozh added a commit to jasozh/react-native that referenced this pull request Oct 21, 2026
Summary:

Fixes [react#34553](react#34553), where the ripple effect is a square even if the underlying Pressable is rounded.

### Root Cause

For bounded ripples, ReactDrawableHelper creates a RippleDrawable with a ColorDrawable mask. This mask is rectangular and remains that way when added to CompositeBackgroundDrawable as a feedback layer, even if a border radius is configured.

### This PR

Modify ReactDrawableHelper to replace the RippleDrawable's rectangular ColorDrawable mask with a BackgroundDrawable. If border radius is set, `getOpacity()` returns `PixelFormat.TRANSLUCENT` which makes RippleDrawable draw the mask into an alpha-only bitmap so the ripple respects the border radius. Otherwise, it returns `PixelFormat.OPAQUE` to avoid the cost of creating the bitmap and match the original ColorDrawable behavior.

Modify BackgroundStyleApplicator to update the ripple mask's border radius during `setFeedbackUnderlay()`, `setFeedbackOverlay()`, and `setBorderRadius()`. Also make `ReactViewGroup.applyNativeForeground()` call `setFeedbackOverlay()` so that foreground ripples are properly clipped.

Changelog: [Android][Fixed] - Fix Pressable ripple leaking past rounded corners

Differential Revision: D123816883
Summary:
Pull Request resolved: react#58945

Fixes [react#34553](react#34553), where the ripple effect is a square even if the underlying Pressable is rounded.

### Root Cause

For bounded ripples, ReactDrawableHelper creates a RippleDrawable with a ColorDrawable mask. This mask is rectangular and remains that way when added to CompositeBackgroundDrawable as a feedback layer, even if a border radius is configured.

### This PR

Modify ReactDrawableHelper to replace the RippleDrawable's rectangular ColorDrawable mask with a BackgroundDrawable. If border radius is set, `getOpacity()` returns `PixelFormat.TRANSLUCENT` which makes RippleDrawable draw the mask into an alpha-only bitmap so the ripple respects the border radius. Otherwise, it returns `PixelFormat.OPAQUE` to avoid the cost of creating the bitmap and match the original ColorDrawable behavior.

Modify BackgroundStyleApplicator to update the ripple mask's border radius during `setFeedbackUnderlay()`, `setFeedbackOverlay()`, and `setBorderRadius()`. Also make `ReactViewGroup.applyNativeForeground()` call `setFeedbackOverlay()` so that foreground ripples are properly clipped.

Changelog: [Android][Fixed] - Fix Pressable ripple leaking past rounded corners

Differential Revision: D123816883
@jasozh
jasozh force-pushed the export-D123816883 branch from e9a4572 to 6570029 Compare October 21, 2026 22:48

@cortinico cortinico left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for sending this @jasozh

import kotlin.math.roundToInt

internal class BackgroundDrawable(
internal open class BackgroundDrawable(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not make this please. Can we use composition here instead of inheritance?

Comment on lines +165 to +175
private class RippleMaskDrawable(context: Context) : BackgroundDrawable(context) {
init {
backgroundColor = Color.WHITE
}

@Deprecated("Deprecated in Java")
override fun getOpacity(): Int =
// RippleDrawable clips to an opaque mask's bounds, but renders a translucent mask into a
// bitmap to clip to its shape. Only pay for the bitmap when there are corners to round.
if (borderRadius?.hasRoundedBorders() == true) PixelFormat.TRANSLUCENT else PixelFormat.OPAQUE
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be refactored or moved to its own class

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. meta-exported p: Facebook Partner: Facebook Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pressable Ripple Animation goes beyond curved border

2 participants