From 041f5a6925f27265570efdd48312381551162581 Mon Sep 17 00:00:00 2001 From: Bahadir10 Date: Sat, 7 Nov 2026 03:10:44 +0300 Subject: [PATCH] Fix ScrollView with refreshControl clipping when overflow: visible on Android Summary: **Problem**: On Android, when a `ScrollView` (or `FlatList`/`SectionList`) has `style={{ overflow: 'visible' }}`, attaching a `refreshControl` causes its content to be clipped to its own bounds. This occurs because `ScrollView` wraps the native scroll view in `AndroidSwipeRefreshLayout` (`ReactSwipeRefreshLayout`), which defaults to Android `ViewGroup`'s `clipChildren = true`. `ReactScrollView` already manages its own clipping in `onDraw` based on its `_overflow` style (`BackgroundStyleApplicator.clipToPaddingBox`), so `ReactSwipeRefreshLayout` should not forcefully clip its children. **Proposed change**: Set `clipChildren = false` in `ReactSwipeRefreshLayout.init` so that `ReactScrollView`'s `overflow: 'visible'` can render unclipped while preserving normal clipping when `overflow` is hidden or scroll. Changelog: [Android][Fixed] - Respect `overflow: 'visible'` on `ScrollView` when `refreshControl` is present --- .../swiperefresh/ReactSwipeRefreshLayout.kt | 4 ++ .../ReactSwipeRefreshLayoutTest.kt | 37 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayoutTest.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.kt index d2c6fc8d6d90..15f995379d5a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.kt @@ -29,6 +29,10 @@ public open class ReactSwipeRefreshLayout(reactContext: ReactContext) : private var intercepted: Boolean = false private var nativeGestureStarted: Boolean = false + init { + clipChildren = false + } + override var hasChildPressedStateDelay: Boolean? = null public override fun setRefreshing(refreshing: Boolean) { diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayoutTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayoutTest.kt new file mode 100644 index 000000000000..7c50b9be85b4 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayoutTest.kt @@ -0,0 +1,37 @@ +/* + * 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.views.swiperefresh + +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactTestHelper.createTestReactApplicationContext +import com.facebook.testutils.shadows.ShadowSoLoader +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +/** Verifies behavior of [ReactSwipeRefreshLayout]. */ +@RunWith(RobolectricTestRunner::class) +@Config(shadows = [ShadowSoLoader::class]) +class ReactSwipeRefreshLayoutTest { + + private lateinit var context: ReactApplicationContext + + @Before + fun setup() { + context = createTestReactApplicationContext() + } + + @Test + fun testClipChildrenIsDisabledByDefault() { + val layout = ReactSwipeRefreshLayout(context) + assertThat(layout.clipChildren).isFalse() + } +}