Skip to content

Commit a21a4b8

Browse files
Mark Verlingierifacebook-github-bot
authored andcommitted
Allow fadingEdgeLength to be set independently on the start and end of the scrollview (#51174)
Summary: Pull Request resolved: #51174 Allows the start and end of the scrollview to be faded to different values independently by passing an object containing a "start" and "end" value to the fadingEdgeLength prop. To support non-uniform edge lengths we use the [FadingEdgeStrength](https://developer.android.com/reference/android/view/View#getTopFadingEdgeStrength()) api to set different values for the start/end of the scrollview. The FadingEdgeStrength value is multiplied by the FadingEdgeLength value [internally by Android when drawing the view.](https://cs.android.com/android/platform/superproject/+/android15-qpr1-release:frameworks/base/core/java/android/view/View.java;l=25007) Because the value of FadingEdgeStrength is required to be between 0 and 1, we set setFadingEdgeLength to the max value and set the smaller side as a percentage of that value. Changelog: [Android][Added] Allow fadingEdgeLength to be set independently on the start and end of the scrollview Reviewed By: martinbooth Differential Revision: D74222606 fbshipit-source-id: 7010ca803cc48450ab98c2a457fdc72ff47c29d7
1 parent 536aa67 commit a21a4b8

11 files changed

Lines changed: 183 additions & 32 deletions

File tree

packages/react-native/Libraries/Components/ScrollView/ScrollView.d.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -565,16 +565,20 @@ export interface ScrollViewPropsAndroid {
565565
nestedScrollEnabled?: boolean | undefined;
566566

567567
/**
568-
* Fades out the edges of the scroll content.
568+
* Controls the fading effect at the edges of the scroll content.
569569
*
570-
* If the value is greater than 0, the fading edges will be set accordingly
571-
* to the current scroll direction and position,
572-
* indicating if there is more content to show.
570+
* A value greater than 0 will apply the fading effect, indicating more content is available
571+
* to scroll.
572+
*
573+
* You can specify a single number to apply the same fading length to both edges.
574+
* Alternatively, use an object with `start` and `end` properties to set different
575+
* fading lengths for the start and end of the scroll content.
573576
*
574577
* The default value is 0.
578+
*
575579
* @platform android
576580
*/
577-
fadingEdgeLength?: number | undefined;
581+
fadingEdgeLength?: number | {start: number; end: number} | undefined;
578582

579583
/**
580584
* Causes the scrollbars not to turn transparent when they are not in use. The default value is false.

packages/react-native/Libraries/Components/ScrollView/ScrollView.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -372,17 +372,20 @@ export type ScrollViewPropsAndroid = $ReadOnly<{
372372
*/
373373
persistentScrollbar?: ?boolean,
374374
/**
375-
* Fades out the edges of the scroll content.
375+
* Controls the fading effect at the edges of the scroll content.
376376
*
377-
* If the value is greater than 0, the fading edges will be set accordingly
378-
* to the current scroll direction and position,
379-
* indicating if there is more content to show.
377+
* A value greater than 0 will apply the fading effect, indicating more content is available
378+
* to scroll.
379+
*
380+
* You can specify a single number to apply the same fading length to both edges.
381+
* Alternatively, use an object with `start` and `end` properties to set different
382+
* fading lengths for the start and end of the scroll content.
380383
*
381384
* The default value is 0.
382385
*
383386
* @platform android
384387
*/
385-
fadingEdgeLength?: ?number,
388+
fadingEdgeLength?: ?number | {start: number, end: number},
386389
}>;
387390

388391
type StickyHeaderComponentType = component(

packages/react-native/Libraries/Components/ScrollView/ScrollViewNativeComponentType.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export type ScrollViewNativeProps = $ReadOnly<{
4242
directionalLockEnabled?: ?boolean,
4343
disableIntervalMomentum?: ?boolean,
4444
endFillColor?: ?ColorValue,
45-
fadingEdgeLength?: ?number,
45+
fadingEdgeLength?: ?number | {start: number, end: number},
4646
indicatorStyle?: ?('default' | 'black' | 'white'),
4747
isInvertedVirtualizedList?: ?boolean,
4848
keyboardDismissMode?: ?('none' | 'on-drag' | 'interactive'),

packages/react-native/Libraries/Lists/FlatList.d.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,16 +155,20 @@ export interface FlatListProps<ItemT> extends VirtualizedListProps<ItemT> {
155155
removeClippedSubviews?: boolean | undefined;
156156

157157
/**
158-
* Fades out the edges of the scroll content.
158+
* Controls the fading effect at the edges of the scroll content.
159159
*
160-
* If the value is greater than 0, the fading edges will be set accordingly
161-
* to the current scroll direction and position,
162-
* indicating if there is more content to show.
160+
* A value greater than 0 will apply the fading effect, indicating more content is available
161+
* to scroll.
162+
*
163+
* You can specify a single number to apply the same fading length to both edges.
164+
* Alternatively, use an object with `start` and `end` properties to set different
165+
* fading lengths for the start and end of the scroll content.
163166
*
164167
* The default value is 0.
168+
*
165169
* @platform android
166170
*/
167-
fadingEdgeLength?: number | undefined;
171+
fadingEdgeLength?: number | {start: number; end: number} | undefined;
168172
}
169173

170174
export abstract class FlatListComponent<

packages/react-native/Libraries/Lists/FlatList.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ type OptionalProps<ItemT> = {
145145
/**
146146
* See `ScrollView` for flow type and further documentation.
147147
*/
148-
fadingEdgeLength?: ?number,
148+
fadingEdgeLength?: ?number | {start: number, end: number},
149149
/**
150150
* Enable an optimization to memoize the item renderer to prevent unnecessary rerenders.
151151
*/

packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2018,7 +2018,7 @@ export type ScrollViewPropsAndroid = $ReadOnly<{
20182018
scrollPerfTag?: ?string,
20192019
overScrollMode?: ?(\\"auto\\" | \\"always\\" | \\"never\\"),
20202020
persistentScrollbar?: ?boolean,
2021-
fadingEdgeLength?: ?number,
2021+
fadingEdgeLength?: ?number | { start: number, end: number },
20222022
}>;
20232023
type StickyHeaderComponentType = component(
20242024
ref?: React.RefSetter<
@@ -2157,7 +2157,7 @@ exports[`public API should not change unintentionally Libraries/Components/Scrol
21572157
directionalLockEnabled?: ?boolean,
21582158
disableIntervalMomentum?: ?boolean,
21592159
endFillColor?: ?ColorValue,
2160-
fadingEdgeLength?: ?number,
2160+
fadingEdgeLength?: ?number | { start: number, end: number },
21612161
indicatorStyle?: ?(\\"default\\" | \\"black\\" | \\"white\\"),
21622162
isInvertedVirtualizedList?: ?boolean,
21632163
keyboardDismissMode?: ?(\\"none\\" | \\"on-drag\\" | \\"interactive\\"),
@@ -4947,7 +4947,7 @@ type OptionalProps<ItemT> = {
49474947
keyExtractor?: ?(item: ItemT, index: number) => string,
49484948
numColumns?: number,
49494949
removeClippedSubviews?: boolean,
4950-
fadingEdgeLength?: ?number,
4950+
fadingEdgeLength?: ?number | { start: number, end: number },
49514951
strictMode?: boolean,
49524952
};
49534953
type FlatListBaseProps<ItemT> = {

packages/react-native/ReactAndroid/api/ReactAndroid.api

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5752,14 +5752,18 @@ public class com/facebook/react/views/scroll/ReactHorizontalScrollView : android
57525752
public fun focusSearch (Landroid/view/View;I)Landroid/view/View;
57535753
public fun getChildVisibleRect (Landroid/view/View;Landroid/graphics/Rect;Landroid/graphics/Point;)Z
57545754
public fun getClippingRect (Landroid/graphics/Rect;)V
5755+
public fun getFadingEdgeLengthEnd ()I
5756+
public fun getFadingEdgeLengthStart ()I
57555757
public fun getFlingAnimator ()Landroid/animation/ValueAnimator;
57565758
public fun getFlingExtrapolatedDistance (I)I
57575759
public fun getLastScrollDispatchTime ()J
5760+
protected fun getLeftFadingEdgeStrength ()F
57585761
public fun getOverflow ()Ljava/lang/String;
57595762
public fun getOverflowInset ()Landroid/graphics/Rect;
57605763
public fun getPointerEvents ()Lcom/facebook/react/uimanager/PointerEvents;
57615764
public fun getReactScrollViewScrollState ()Lcom/facebook/react/views/scroll/ReactScrollViewHelper$ReactScrollViewScrollState;
57625765
public fun getRemoveClippedSubviews ()Z
5766+
protected fun getRightFadingEdgeStrength ()F
57635767
public fun getScrollEnabled ()Z
57645768
public fun getScrollEventThrottle ()I
57655769
public fun getStateWrapper ()Lcom/facebook/react/uimanager/StateWrapper;
@@ -5793,6 +5797,8 @@ public class com/facebook/react/views/scroll/ReactHorizontalScrollView : android
57935797
public fun setDecelerationRate (F)V
57945798
public fun setDisableIntervalMomentum (Z)V
57955799
public fun setEndFillColor (I)V
5800+
public fun setFadingEdgeLengthEnd (I)V
5801+
public fun setFadingEdgeLengthStart (I)V
57965802
public fun setLastScrollDispatchTime (J)V
57975803
public fun setMaintainVisibleContentPosition (Lcom/facebook/react/views/scroll/MaintainVisibleScrollPositionHelper$Config;)V
57985804
public fun setOverflow (Ljava/lang/String;)V
@@ -5842,7 +5848,7 @@ public class com/facebook/react/views/scroll/ReactHorizontalScrollViewManager :
58425848
public final fun setContentOffset (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;Lcom/facebook/react/bridge/ReadableMap;)V
58435849
public final fun setDecelerationRate (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;F)V
58445850
public final fun setDisableIntervalMomentum (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;Z)V
5845-
public final fun setFadingEdgeLength (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;I)V
5851+
public final fun setFadingEdgeLength (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;Lcom/facebook/react/bridge/Dynamic;)V
58465852
public final fun setHorizontal (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;Z)V
58475853
public final fun setMaintainVisibleContentPosition (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;Lcom/facebook/react/bridge/ReadableMap;)V
58485854
public final fun setNestedScrollEnabled (Lcom/facebook/react/views/scroll/ReactHorizontalScrollView;Z)V
@@ -5879,8 +5885,11 @@ public class com/facebook/react/views/scroll/ReactScrollView : android/widget/Sc
58795885
public fun flashScrollIndicators ()V
58805886
public fun fling (I)V
58815887
public fun focusSearch (Landroid/view/View;I)Landroid/view/View;
5888+
protected fun getBottomFadingEdgeStrength ()F
58825889
public fun getChildVisibleRect (Landroid/view/View;Landroid/graphics/Rect;Landroid/graphics/Point;)Z
58835890
public fun getClippingRect (Landroid/graphics/Rect;)V
5891+
public fun getFadingEdgeLengthEnd ()I
5892+
public fun getFadingEdgeLengthStart ()I
58845893
public fun getFlingAnimator ()Landroid/animation/ValueAnimator;
58855894
public fun getFlingExtrapolatedDistance (I)I
58865895
public fun getLastScrollDispatchTime ()J
@@ -5892,6 +5901,7 @@ public class com/facebook/react/views/scroll/ReactScrollView : android/widget/Sc
58925901
public fun getScrollEnabled ()Z
58935902
public fun getScrollEventThrottle ()I
58945903
public fun getStateWrapper ()Lcom/facebook/react/uimanager/StateWrapper;
5904+
protected fun getTopFadingEdgeStrength ()F
58955905
protected fun handleInterceptedTouchEvent (Landroid/view/MotionEvent;)V
58965906
public fun isPartiallyScrolledInView (Landroid/view/View;)Z
58975907
protected fun onAttachedToWindow ()V
@@ -5922,6 +5932,8 @@ public class com/facebook/react/views/scroll/ReactScrollView : android/widget/Sc
59225932
public fun setDecelerationRate (F)V
59235933
public fun setDisableIntervalMomentum (Z)V
59245934
public fun setEndFillColor (I)V
5935+
public fun setFadingEdgeLengthEnd (I)V
5936+
public fun setFadingEdgeLengthStart (I)V
59255937
public fun setLastScrollDispatchTime (J)V
59265938
public fun setMaintainVisibleContentPosition (Lcom/facebook/react/views/scroll/MaintainVisibleScrollPositionHelper$Config;)V
59275939
public fun setOverflow (Ljava/lang/String;)V
@@ -6097,7 +6109,7 @@ public class com/facebook/react/views/scroll/ReactScrollViewManager : com/facebo
60976109
public final fun setContentOffset (Lcom/facebook/react/views/scroll/ReactScrollView;Lcom/facebook/react/bridge/ReadableMap;)V
60986110
public final fun setDecelerationRate (Lcom/facebook/react/views/scroll/ReactScrollView;F)V
60996111
public final fun setDisableIntervalMomentum (Lcom/facebook/react/views/scroll/ReactScrollView;Z)V
6100-
public final fun setFadingEdgeLength (Lcom/facebook/react/views/scroll/ReactScrollView;I)V
6112+
public final fun setFadingEdgeLength (Lcom/facebook/react/views/scroll/ReactScrollView;Lcom/facebook/react/bridge/Dynamic;)V
61016113
public final fun setHorizontal (Lcom/facebook/react/views/scroll/ReactScrollView;Z)V
61026114
public final fun setIsInvertedVirtualizedList (Lcom/facebook/react/views/scroll/ReactScrollView;Z)V
61036115
public final fun setMaintainVisibleContentPosition (Lcom/facebook/react/views/scroll/ReactScrollView;Lcom/facebook/react/bridge/ReadableMap;)V

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
130130
private int mScrollEventThrottle = 0;
131131
private @Nullable View mContentView;
132132
private @Nullable MaintainVisibleScrollPositionHelper mMaintainVisibleContentPositionHelper;
133+
private int mFadingEdgeLengthStart = 0;
134+
private int mFadingEdgeLengthEnd = 0;
133135

134136
private final Rect mTempRect = new Rect();
135137

@@ -284,6 +286,44 @@ public void flashScrollIndicators() {
284286
awakenScrollBars();
285287
}
286288

289+
public int getFadingEdgeLengthStart() {
290+
return mFadingEdgeLengthStart;
291+
}
292+
293+
public int getFadingEdgeLengthEnd() {
294+
return mFadingEdgeLengthEnd;
295+
}
296+
297+
public void setFadingEdgeLengthStart(int start) {
298+
mFadingEdgeLengthStart = start;
299+
invalidate();
300+
}
301+
302+
public void setFadingEdgeLengthEnd(int end) {
303+
mFadingEdgeLengthEnd = end;
304+
invalidate();
305+
}
306+
307+
@Override
308+
protected float getLeftFadingEdgeStrength() {
309+
float max = Math.max(mFadingEdgeLengthStart, mFadingEdgeLengthEnd);
310+
int value =
311+
getLayoutDirection() == LAYOUT_DIRECTION_RTL
312+
? mFadingEdgeLengthEnd
313+
: mFadingEdgeLengthStart;
314+
return (value / max);
315+
}
316+
317+
@Override
318+
protected float getRightFadingEdgeStrength() {
319+
float max = Math.max(mFadingEdgeLengthStart, mFadingEdgeLengthEnd);
320+
int value =
321+
getLayoutDirection() == LAYOUT_DIRECTION_RTL
322+
? mFadingEdgeLengthStart
323+
: mFadingEdgeLengthEnd;
324+
return (value / max);
325+
}
326+
287327
public void setOverflow(@Nullable String overflow) {
288328
if (overflow == null) {
289329
mOverflow = Overflow.SCROLL;

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.kt

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ package com.facebook.react.views.scroll
99

1010
import android.graphics.Color
1111
import androidx.core.view.ViewCompat
12+
import com.facebook.react.bridge.Dynamic
1213
import com.facebook.react.bridge.ReadableArray
1314
import com.facebook.react.bridge.ReadableMap
15+
import com.facebook.react.bridge.ReadableType
1416
import com.facebook.react.bridge.RetryableMountingLayerException
1517
import com.facebook.react.module.annotations.ReactModule
1618
import com.facebook.react.uimanager.BackgroundStyleApplicator.setBorderColor
@@ -19,6 +21,7 @@ import com.facebook.react.uimanager.BackgroundStyleApplicator.setBorderStyle
1921
import com.facebook.react.uimanager.BackgroundStyleApplicator.setBorderWidth
2022
import com.facebook.react.uimanager.LengthPercentage
2123
import com.facebook.react.uimanager.LengthPercentageType
24+
import com.facebook.react.uimanager.PixelUtil.dpToPx
2225
import com.facebook.react.uimanager.PixelUtil.getDisplayMetricDensity
2326
import com.facebook.react.uimanager.PixelUtil.toPixelFromDIP
2427
import com.facebook.react.uimanager.PointerEvents.Companion.parsePointerEvents
@@ -308,12 +311,37 @@ constructor(private val fpsListener: FpsListener? = null) :
308311
}
309312

310313
@ReactProp(name = "fadingEdgeLength")
311-
public fun setFadingEdgeLength(view: ReactHorizontalScrollView, value: Int) {
312-
if (value > 0) {
313-
view.isHorizontalFadingEdgeEnabled = true
314-
view.setFadingEdgeLength(value)
314+
public fun setFadingEdgeLength(view: ReactHorizontalScrollView, value: Dynamic) {
315+
when (value.type) {
316+
ReadableType.Number -> {
317+
view.setFadingEdgeLengthStart(value.asInt())
318+
view.setFadingEdgeLengthEnd(value.asInt())
319+
}
320+
ReadableType.Map -> {
321+
value.asMap()?.let { map ->
322+
var start = 0
323+
var end = 0
324+
if (map.hasKey("start") && map.getInt("start") > 0) {
325+
start = map.getInt("start")
326+
}
327+
if (map.hasKey("end") && map.getInt("end") > 0) {
328+
end = map.getInt("end")
329+
}
330+
view.setFadingEdgeLengthStart(start)
331+
view.setFadingEdgeLengthEnd(end)
332+
}
333+
}
334+
else -> {
335+
// no-op
336+
}
337+
}
338+
if (view.getFadingEdgeLengthStart() > 0 || view.getFadingEdgeLengthEnd() > 0) {
339+
view.setHorizontalFadingEdgeEnabled(true)
340+
view.setFadingEdgeLength(
341+
Math.round(
342+
Math.max(view.getFadingEdgeLengthStart(), view.getFadingEdgeLengthEnd()).dpToPx()))
315343
} else {
316-
view.isHorizontalFadingEdgeEnabled = false
344+
view.setHorizontalFadingEdgeEnabled(false)
317345
view.setFadingEdgeLength(0)
318346
}
319347
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ public class ReactScrollView extends ScrollView
131131
private int mScrollEventThrottle = 0;
132132
private @Nullable MaintainVisibleScrollPositionHelper mMaintainVisibleContentPositionHelper =
133133
null;
134+
private int mFadingEdgeLengthStart = 0;
135+
private int mFadingEdgeLengthEnd = 0;
134136

135137
public ReactScrollView(Context context) {
136138
this(context, null);
@@ -263,6 +265,36 @@ public void flashScrollIndicators() {
263265
awakenScrollBars();
264266
}
265267

268+
public int getFadingEdgeLengthStart() {
269+
return mFadingEdgeLengthStart;
270+
}
271+
272+
public int getFadingEdgeLengthEnd() {
273+
return mFadingEdgeLengthEnd;
274+
}
275+
276+
public void setFadingEdgeLengthStart(int start) {
277+
mFadingEdgeLengthStart = start;
278+
invalidate();
279+
}
280+
281+
public void setFadingEdgeLengthEnd(int end) {
282+
mFadingEdgeLengthEnd = end;
283+
invalidate();
284+
}
285+
286+
@Override
287+
protected float getTopFadingEdgeStrength() {
288+
float max = Math.max(mFadingEdgeLengthStart, mFadingEdgeLengthEnd);
289+
return (mFadingEdgeLengthStart / max);
290+
}
291+
292+
@Override
293+
protected float getBottomFadingEdgeStrength() {
294+
float max = Math.max(mFadingEdgeLengthStart, mFadingEdgeLengthEnd);
295+
return (mFadingEdgeLengthEnd / max);
296+
}
297+
266298
public void setOverflow(@Nullable String overflow) {
267299
if (overflow == null) {
268300
mOverflow = Overflow.SCROLL;

0 commit comments

Comments
 (0)