react-native-gesture-handler icon indicating copy to clipboard operation
react-native-gesture-handler copied to clipboard

Pressable with border inside View with overflow: 'hidden' gets cut off on Android

Open gigobyte opened this issue 5 months ago • 6 comments

Description

The handling of borderWidth for RNGH Pressable seems to be different than Pressable from 'react-native'. The clipping on the screenshot below is observable both with horizontal and vertical FlatLists. It also doesn't matter if FlatList from RNGH or RN is used, both produce the same results.

This issue only happens on Android and can be observed on debug/release build, both emulator and real devices consistently.

Image

The fix seems to be to move the styles from the Pressable to a nested View.

Steps to reproduce

  1. Example code is provided in the gist

A link to a Gist, an Expo Snack or a link to a repository based on this template that reproduces the bug.

https://gist.github.com/gigobyte/56574a3b2f95223f5bf40fb5b2d43efd

Gesture Handler version

2.27.2

React Native version

0.79.0

Platforms

Android

JavaScript runtime

None

Workflow

None

Architecture

New Architecture (Fabric)

Build type

None

Device

None

Device model

No response

Acknowledgements

Yes

gigobyte avatar Aug 11 '25 10:08 gigobyte

Hey! 👋

The issue doesn't seem to contain a minimal reproduction.

Could you provide a snack or a link to a GitHub repository under your username that reproduces the problem?

github-actions[bot] avatar Aug 11 '25 10:08 github-actions[bot]

I have narrowed down this issue to a faulty combination of border and overflow: 'hidden' because I can also reproduce this with a normal View:

<View style={{ overflow: 'hidden' }}>
  <Pressable
    style={{
      borderWidth: 1,
      width: 100,
      height: 50,
    }}
  />
</View>
Image

gigobyte avatar Aug 11 '25 11:08 gigobyte

I’m running into the same issue, and while the suggested workaround (wrapping the Pressable content inside an additional View) technically fixes the clipping, it creates problems on the architecture side in a real-world project with reusable components.

In our case, using a pattern like this becomes problematic:

const ButtonScaleComponent: FC<ButtonScaleProps> = ({
  children,
  style,
  disabled = false,
  onPress,
  onLongPress,
  onLayout,
}) => {
  const pressed = useSharedValue(false)

  const scale = useDerivedValue(() => {
    if (disabled) return withTiming(1, { duration: 150 })
    return pressed.value
      ? withTiming(0.97, { duration: 100 })
      : withTiming(1, { duration: 150 })
  })

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }],
  }))

  return (
    <AnimatedPressable
      style={[animatedStyle, style]}
      disabled={disabled}
      onPress={onPress}
      onPressIn={() => (pressed.value = true)}
      onPressOut={() => (pressed.value = false)}
      onLongPress={onLongPress}
      onLayout={onLayout}
    >
      {children}
    </AnimatedPressable>
  )
}

If we add an extra View inside the Pressable just to avoid the bug, we’re forced to:

  • split styling between the Pressable and the inner View,
  • expose two different style props (e.g. pressableStyle and contentStyle),
  • change the API of a component that should be simple and reusable,
  • and propagate this complexity everywhere the component is used.

For small components this may seem minor, but in a larger codebase with many reusable UI primitives, it creates a lot of unnecessary overhead and breaks the internal logic of our components.

So while the workaround “works”, it’s not really practical in a scalable architecture. A proper fix would help avoid these kinds of structural issues.

TomCorvus avatar Dec 04 '25 18:12 TomCorvus

@j-piasecki Sorry for tagging you, but it seems this issue may have been forgotten. Do you think someone could take a look at it or consider fixing it?

TomCorvus avatar Dec 04 '25 18:12 TomCorvus

@TomCorvus We fixed it using this patch

diff --git a/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt b/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt
index d51501120abdf073c852ca880cdccddfbe941684..164303f39fe5fe5b8a517baef1ca415a9fb64ede 100644
--- a/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt
+++ b/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt
@@ -13,6 +13,7 @@ import android.graphics.drawable.LayerDrawable
 import android.graphics.drawable.PaintDrawable
 import android.graphics.drawable.RippleDrawable
 import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.InsetDrawable
 import android.graphics.drawable.shapes.RectShape
 import android.os.Build
 import android.util.TypedValue
@@ -385,6 +386,11 @@ class RNGestureHandlerButtonViewManager :
         }
       }
 
+      // https://github.com/software-mansion/react-native-gesture-handler/issues/3668
+      if (borderWidth > 0f) {
+        val inset = Math.ceil((borderWidth / 2f).toDouble()).toInt()
+        return InsetDrawable(borderDrawable, inset)
+      }
       return borderDrawable
     }

gigobyte avatar Dec 05 '25 07:12 gigobyte

Thanks, @gigobyte. Do you think this is a long-term solution or just a quick fix?

TomCorvus avatar Dec 09 '25 07:12 TomCorvus

It's a quick fix, there are other border-related issues and it looks like a general fix was started but ultimately abandoned.

@j-piasecki Could you share some information about that PR, why has progress stalled?

gigobyte avatar Dec 19 '25 14:12 gigobyte