diff --git a/packages/react-native/Libraries/Components/ScrollView/ScrollView.d.ts b/packages/react-native/Libraries/Components/ScrollView/ScrollView.d.ts index b4c347f1998410..d476e33eedae50 100644 --- a/packages/react-native/Libraries/Components/ScrollView/ScrollView.d.ts +++ b/packages/react-native/Libraries/Components/ScrollView/ScrollView.d.ts @@ -565,16 +565,20 @@ export interface ScrollViewPropsAndroid { nestedScrollEnabled?: boolean | undefined; /** - * Fades out the edges of the scroll content. + * Controls the fading effect at the edges of the scroll content. * - * If the value is greater than 0, the fading edges will be set accordingly - * to the current scroll direction and position, - * indicating if there is more content to show. + * A value greater than 0 will apply the fading effect, indicating more content is available + * to scroll. + * + * You can specify a single number to apply the same fading length to both edges. + * Alternatively, use an object with `start` and `end` properties to set different + * fading lengths for the start and end of the scroll content. * * The default value is 0. + * * @platform android */ - fadingEdgeLength?: number | undefined; + fadingEdgeLength?: number | {start: number; end: number} | undefined; /** * Causes the scrollbars not to turn transparent when they are not in use. The default value is false. diff --git a/packages/react-native/Libraries/Components/ScrollView/ScrollView.js b/packages/react-native/Libraries/Components/ScrollView/ScrollView.js index efd663245ed531..95754d5cd36d6f 100644 --- a/packages/react-native/Libraries/Components/ScrollView/ScrollView.js +++ b/packages/react-native/Libraries/Components/ScrollView/ScrollView.js @@ -372,17 +372,20 @@ export type ScrollViewPropsAndroid = $ReadOnly<{ */ persistentScrollbar?: ?boolean, /** - * Fades out the edges of the scroll content. + * Controls the fading effect at the edges of the scroll content. * - * If the value is greater than 0, the fading edges will be set accordingly - * to the current scroll direction and position, - * indicating if there is more content to show. + * A value greater than 0 will apply the fading effect, indicating more content is available + * to scroll. + * + * You can specify a single number to apply the same fading length to both edges. + * Alternatively, use an object with `start` and `end` properties to set different + * fading lengths for the start and end of the scroll content. * * The default value is 0. * * @platform android */ - fadingEdgeLength?: ?number, + fadingEdgeLength?: ?number | {start: number, end: number}, }>; type StickyHeaderComponentType = component( diff --git a/packages/react-native/Libraries/Components/ScrollView/ScrollViewNativeComponentType.js b/packages/react-native/Libraries/Components/ScrollView/ScrollViewNativeComponentType.js index 8412aa9dff4859..1feee6999541c0 100644 --- a/packages/react-native/Libraries/Components/ScrollView/ScrollViewNativeComponentType.js +++ b/packages/react-native/Libraries/Components/ScrollView/ScrollViewNativeComponentType.js @@ -42,7 +42,7 @@ export type ScrollViewNativeProps = $ReadOnly<{ directionalLockEnabled?: ?boolean, disableIntervalMomentum?: ?boolean, endFillColor?: ?ColorValue, - fadingEdgeLength?: ?number, + fadingEdgeLength?: ?number | {start: number, end: number}, indicatorStyle?: ?('default' | 'black' | 'white'), isInvertedVirtualizedList?: ?boolean, keyboardDismissMode?: ?('none' | 'on-drag' | 'interactive'), diff --git a/packages/react-native/Libraries/Lists/FlatList.d.ts b/packages/react-native/Libraries/Lists/FlatList.d.ts index b09987a3f5ad9a..06c4bca1f52869 100644 --- a/packages/react-native/Libraries/Lists/FlatList.d.ts +++ b/packages/react-native/Libraries/Lists/FlatList.d.ts @@ -155,16 +155,20 @@ export interface FlatListProps extends VirtualizedListProps { removeClippedSubviews?: boolean | undefined; /** - * Fades out the edges of the scroll content. + * Controls the fading effect at the edges of the scroll content. * - * If the value is greater than 0, the fading edges will be set accordingly - * to the current scroll direction and position, - * indicating if there is more content to show. + * A value greater than 0 will apply the fading effect, indicating more content is available + * to scroll. + * + * You can specify a single number to apply the same fading length to both edges. + * Alternatively, use an object with `start` and `end` properties to set different + * fading lengths for the start and end of the scroll content. * * The default value is 0. + * * @platform android */ - fadingEdgeLength?: number | undefined; + fadingEdgeLength?: number | {start: number; end: number} | undefined; } export abstract class FlatListComponent< diff --git a/packages/react-native/Libraries/Lists/FlatList.js b/packages/react-native/Libraries/Lists/FlatList.js index 6487484f834f83..f3e28fbce8c894 100644 --- a/packages/react-native/Libraries/Lists/FlatList.js +++ b/packages/react-native/Libraries/Lists/FlatList.js @@ -145,7 +145,7 @@ type OptionalProps = { /** * See `ScrollView` for flow type and further documentation. */ - fadingEdgeLength?: ?number, + fadingEdgeLength?: ?number | {start: number, end: number}, /** * Enable an optimization to memoize the item renderer to prevent unnecessary rerenders. */ diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index 096c639c7b1296..dc044f8b4c8816 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -2018,7 +2018,7 @@ export type ScrollViewPropsAndroid = $ReadOnly<{ scrollPerfTag?: ?string, overScrollMode?: ?(\\"auto\\" | \\"always\\" | \\"never\\"), persistentScrollbar?: ?boolean, - fadingEdgeLength?: ?number, + fadingEdgeLength?: ?number | { start: number, end: number }, }>; type StickyHeaderComponentType = component( ref?: React.RefSetter< @@ -2157,7 +2157,7 @@ exports[`public API should not change unintentionally Libraries/Components/Scrol directionalLockEnabled?: ?boolean, disableIntervalMomentum?: ?boolean, endFillColor?: ?ColorValue, - fadingEdgeLength?: ?number, + fadingEdgeLength?: ?number | { start: number, end: number }, indicatorStyle?: ?(\\"default\\" | \\"black\\" | \\"white\\"), isInvertedVirtualizedList?: ?boolean, keyboardDismissMode?: ?(\\"none\\" | \\"on-drag\\" | \\"interactive\\"), @@ -4934,7 +4934,7 @@ type OptionalProps = { keyExtractor?: ?(item: ItemT, index: number) => string, numColumns?: number, removeClippedSubviews?: boolean, - fadingEdgeLength?: ?number, + fadingEdgeLength?: ?number | { start: number, end: number }, strictMode?: boolean, }; type FlatListBaseProps = { diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 246d8051156074..9f1802215da2b7 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -5764,14 +5764,18 @@ public class com/facebook/react/views/scroll/ReactHorizontalScrollView : android public fun focusSearch (Landroid/view/View;I)Landroid/view/View; public fun getChildVisibleRect (Landroid/view/View;Landroid/graphics/Rect;Landroid/graphics/Point;)Z public fun getClippingRect (Landroid/graphics/Rect;)V + public fun getFadingEdgeLengthEnd ()I + public fun getFadingEdgeLengthStart ()I public fun getFlingAnimator ()Landroid/animation/ValueAnimator; public fun getFlingExtrapolatedDistance (I)I public fun getLastScrollDispatchTime ()J + protected fun getLeftFadingEdgeStrength ()F public fun getOverflow ()Ljava/lang/String; public fun getOverflowInset ()Landroid/graphics/Rect; public fun getPointerEvents ()Lcom/facebook/react/uimanager/PointerEvents; public fun getReactScrollViewScrollState ()Lcom/facebook/react/views/scroll/ReactScrollViewHelper$ReactScrollViewScrollState; public fun getRemoveClippedSubviews ()Z + protected fun getRightFadingEdgeStrength ()F public fun getScrollEnabled ()Z public fun getScrollEventThrottle ()I public fun getStateWrapper ()Lcom/facebook/react/uimanager/StateWrapper; @@ -5805,6 +5809,8 @@ public class com/facebook/react/views/scroll/ReactHorizontalScrollView : android public fun setDecelerationRate (F)V public fun setDisableIntervalMomentum (Z)V public fun setEndFillColor (I)V + public fun setFadingEdgeLengthEnd (I)V + public fun setFadingEdgeLengthStart (I)V public fun setLastScrollDispatchTime (J)V public fun setMaintainVisibleContentPosition (Lcom/facebook/react/views/scroll/MaintainVisibleScrollPositionHelper$Config;)V public fun setOverflow (Ljava/lang/String;)V @@ -5854,7 +5860,7 @@ public class com/facebook/react/views/scroll/ReactHorizontalScrollViewManager : public final fun setContentOffset (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;Lcom/facebook/react/bridge/ReadableMap;)V public final fun setDecelerationRate (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;F)V public final fun setDisableIntervalMomentum (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;Z)V - public final fun setFadingEdgeLength (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;I)V + public final fun setFadingEdgeLength (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;Lcom/facebook/react/bridge/Dynamic;)V public final fun setHorizontal (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;Z)V public final fun setMaintainVisibleContentPosition (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;Lcom/facebook/react/bridge/ReadableMap;)V public final fun setNestedScrollEnabled (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;Z)V @@ -5891,8 +5897,11 @@ public class com/facebook/react/views/scroll/ReactScrollView : android/widget/Sc public fun flashScrollIndicators ()V public fun fling (I)V public fun focusSearch (Landroid/view/View;I)Landroid/view/View; + protected fun getBottomFadingEdgeStrength ()F public fun getChildVisibleRect (Landroid/view/View;Landroid/graphics/Rect;Landroid/graphics/Point;)Z public fun getClippingRect (Landroid/graphics/Rect;)V + public fun getFadingEdgeLengthEnd ()I + public fun getFadingEdgeLengthStart ()I public fun getFlingAnimator ()Landroid/animation/ValueAnimator; public fun getFlingExtrapolatedDistance (I)I public fun getLastScrollDispatchTime ()J @@ -5904,6 +5913,7 @@ public class com/facebook/react/views/scroll/ReactScrollView : android/widget/Sc public fun getScrollEnabled ()Z public fun getScrollEventThrottle ()I public fun getStateWrapper ()Lcom/facebook/react/uimanager/StateWrapper; + protected fun getTopFadingEdgeStrength ()F protected fun handleInterceptedTouchEvent (Landroid/view/MotionEvent;)V public fun isPartiallyScrolledInView (Landroid/view/View;)Z protected fun onAttachedToWindow ()V @@ -5934,6 +5944,8 @@ public class com/facebook/react/views/scroll/ReactScrollView : android/widget/Sc public fun setDecelerationRate (F)V public fun setDisableIntervalMomentum (Z)V public fun setEndFillColor (I)V + public fun setFadingEdgeLengthEnd (I)V + public fun setFadingEdgeLengthStart (I)V public fun setLastScrollDispatchTime (J)V public fun setMaintainVisibleContentPosition (Lcom/facebook/react/views/scroll/MaintainVisibleScrollPositionHelper$Config;)V public fun setOverflow (Ljava/lang/String;)V @@ -6109,7 +6121,7 @@ public class com/facebook/react/views/scroll/ReactScrollViewManager : com/facebo public final fun setContentOffset (Lcom/facebook/react/views/scroll/ReactScrollView;Lcom/facebook/react/bridge/ReadableMap;)V public final fun setDecelerationRate (Lcom/facebook/react/views/scroll/ReactScrollView;F)V public final fun setDisableIntervalMomentum (Lcom/facebook/react/views/scroll/ReactScrollView;Z)V - public final fun setFadingEdgeLength (Lcom/facebook/react/views/scroll/ReactScrollView;I)V + public final fun setFadingEdgeLength (Lcom/facebook/react/views/scroll/ReactScrollView;Lcom/facebook/react/bridge/Dynamic;)V public final fun setHorizontal (Lcom/facebook/react/views/scroll/ReactScrollView;Z)V public final fun setIsInvertedVirtualizedList (Lcom/facebook/react/views/scroll/ReactScrollView;Z)V public final fun setMaintainVisibleContentPosition (Lcom/facebook/react/views/scroll/ReactScrollView;Lcom/facebook/react/bridge/ReadableMap;)V diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java index 82c60e7b8fc661..690e64b581f3c2 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java @@ -130,6 +130,8 @@ public class ReactHorizontalScrollView extends HorizontalScrollView private int mScrollEventThrottle = 0; private @Nullable View mContentView; private @Nullable MaintainVisibleScrollPositionHelper mMaintainVisibleContentPositionHelper; + private int mFadingEdgeLengthStart = 0; + private int mFadingEdgeLengthEnd = 0; private final Rect mTempRect = new Rect(); @@ -284,6 +286,44 @@ public void flashScrollIndicators() { awakenScrollBars(); } + public int getFadingEdgeLengthStart() { + return mFadingEdgeLengthStart; + } + + public int getFadingEdgeLengthEnd() { + return mFadingEdgeLengthEnd; + } + + public void setFadingEdgeLengthStart(int start) { + mFadingEdgeLengthStart = start; + invalidate(); + } + + public void setFadingEdgeLengthEnd(int end) { + mFadingEdgeLengthEnd = end; + invalidate(); + } + + @Override + protected float getLeftFadingEdgeStrength() { + float max = Math.max(mFadingEdgeLengthStart, mFadingEdgeLengthEnd); + int value = + getLayoutDirection() == LAYOUT_DIRECTION_RTL + ? mFadingEdgeLengthEnd + : mFadingEdgeLengthStart; + return (value / max); + } + + @Override + protected float getRightFadingEdgeStrength() { + float max = Math.max(mFadingEdgeLengthStart, mFadingEdgeLengthEnd); + int value = + getLayoutDirection() == LAYOUT_DIRECTION_RTL + ? mFadingEdgeLengthStart + : mFadingEdgeLengthEnd; + return (value / max); + } + public void setOverflow(@Nullable String overflow) { if (overflow == null) { mOverflow = Overflow.SCROLL; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.kt index 253d67ac3d88a5..b2323ea890b524 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.kt @@ -9,8 +9,10 @@ package com.facebook.react.views.scroll import android.graphics.Color import androidx.core.view.ViewCompat +import com.facebook.react.bridge.Dynamic import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.ReadableType import com.facebook.react.bridge.RetryableMountingLayerException import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.BackgroundStyleApplicator.setBorderColor @@ -19,6 +21,7 @@ import com.facebook.react.uimanager.BackgroundStyleApplicator.setBorderStyle import com.facebook.react.uimanager.BackgroundStyleApplicator.setBorderWidth import com.facebook.react.uimanager.LengthPercentage import com.facebook.react.uimanager.LengthPercentageType +import com.facebook.react.uimanager.PixelUtil.dpToPx import com.facebook.react.uimanager.PixelUtil.getDisplayMetricDensity import com.facebook.react.uimanager.PixelUtil.toPixelFromDIP import com.facebook.react.uimanager.PointerEvents.Companion.parsePointerEvents @@ -308,12 +311,37 @@ constructor(private val fpsListener: FpsListener? = null) : } @ReactProp(name = "fadingEdgeLength") - public fun setFadingEdgeLength(view: ReactHorizontalScrollView, value: Int) { - if (value > 0) { - view.isHorizontalFadingEdgeEnabled = true - view.setFadingEdgeLength(value) + public fun setFadingEdgeLength(view: ReactHorizontalScrollView, value: Dynamic) { + when (value.type) { + ReadableType.Number -> { + view.setFadingEdgeLengthStart(value.asInt()) + view.setFadingEdgeLengthEnd(value.asInt()) + } + ReadableType.Map -> { + value.asMap()?.let { map -> + var start = 0 + var end = 0 + if (map.hasKey("start") && map.getInt("start") > 0) { + start = map.getInt("start") + } + if (map.hasKey("end") && map.getInt("end") > 0) { + end = map.getInt("end") + } + view.setFadingEdgeLengthStart(start) + view.setFadingEdgeLengthEnd(end) + } + } + else -> { + // no-op + } + } + if (view.getFadingEdgeLengthStart() > 0 || view.getFadingEdgeLengthEnd() > 0) { + view.setHorizontalFadingEdgeEnabled(true) + view.setFadingEdgeLength( + Math.round( + Math.max(view.getFadingEdgeLengthStart(), view.getFadingEdgeLengthEnd()).dpToPx())) } else { - view.isHorizontalFadingEdgeEnabled = false + view.setHorizontalFadingEdgeEnabled(false) view.setFadingEdgeLength(0) } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java index 2e0c20dfa0dc15..c36e5731538b45 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java @@ -131,6 +131,8 @@ public class ReactScrollView extends ScrollView private int mScrollEventThrottle = 0; private @Nullable MaintainVisibleScrollPositionHelper mMaintainVisibleContentPositionHelper = null; + private int mFadingEdgeLengthStart = 0; + private int mFadingEdgeLengthEnd = 0; public ReactScrollView(Context context) { this(context, null); @@ -263,6 +265,36 @@ public void flashScrollIndicators() { awakenScrollBars(); } + public int getFadingEdgeLengthStart() { + return mFadingEdgeLengthStart; + } + + public int getFadingEdgeLengthEnd() { + return mFadingEdgeLengthEnd; + } + + public void setFadingEdgeLengthStart(int start) { + mFadingEdgeLengthStart = start; + invalidate(); + } + + public void setFadingEdgeLengthEnd(int end) { + mFadingEdgeLengthEnd = end; + invalidate(); + } + + @Override + protected float getTopFadingEdgeStrength() { + float max = Math.max(mFadingEdgeLengthStart, mFadingEdgeLengthEnd); + return (mFadingEdgeLengthStart / max); + } + + @Override + protected float getBottomFadingEdgeStrength() { + float max = Math.max(mFadingEdgeLengthStart, mFadingEdgeLengthEnd); + return (mFadingEdgeLengthEnd / max); + } + public void setOverflow(@Nullable String overflow) { if (overflow == null) { mOverflow = Overflow.SCROLL; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.kt index 6ee0779722e629..b1a17555cbbc13 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.kt @@ -10,8 +10,10 @@ package com.facebook.react.views.scroll import android.graphics.Color import android.view.View import androidx.core.view.ViewCompat +import com.facebook.react.bridge.Dynamic import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.ReadableType import com.facebook.react.bridge.RetryableMountingLayerException import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.BackgroundStyleApplicator.setBorderColor @@ -20,6 +22,7 @@ import com.facebook.react.uimanager.BackgroundStyleApplicator.setBorderStyle import com.facebook.react.uimanager.BackgroundStyleApplicator.setBorderWidth import com.facebook.react.uimanager.LengthPercentage import com.facebook.react.uimanager.LengthPercentageType +import com.facebook.react.uimanager.PixelUtil.dpToPx import com.facebook.react.uimanager.PixelUtil.getDisplayMetricDensity import com.facebook.react.uimanager.PointerEvents.Companion.parsePointerEvents import com.facebook.react.uimanager.ReactClippingViewGroupHelper @@ -297,12 +300,37 @@ constructor(private val fpsListener: FpsListener? = null) : } @ReactProp(name = "fadingEdgeLength") - public fun setFadingEdgeLength(view: ReactScrollView, value: Int) { - if (value > 0) { - view.isVerticalFadingEdgeEnabled = true - view.setFadingEdgeLength(value) + public fun setFadingEdgeLength(view: ReactScrollView, value: Dynamic) { + when (value.type) { + ReadableType.Number -> { + view.setFadingEdgeLengthStart(value.asInt()) + view.setFadingEdgeLengthEnd(value.asInt()) + } + ReadableType.Map -> { + value.asMap()?.let { map -> + var start = 0 + var end = 0 + if (map.hasKey("start") && map.getInt("start") > 0) { + start = map.getInt("start") + } + if (map.hasKey("end") && map.getInt("end") > 0) { + end = map.getInt("end") + } + view.setFadingEdgeLengthStart(start) + view.setFadingEdgeLengthEnd(end) + } + } + else -> { + // no-op + } + } + if (view.getFadingEdgeLengthStart() > 0 || view.getFadingEdgeLengthEnd() > 0) { + view.setVerticalFadingEdgeEnabled(true) + view.setFadingEdgeLength( + Math.round( + Math.max(view.getFadingEdgeLengthStart(), view.getFadingEdgeLengthEnd()).dpToPx())) } else { - view.isVerticalFadingEdgeEnabled = false + view.setVerticalFadingEdgeEnabled(false) view.setFadingEdgeLength(0) } }