3232 * ✔ bindscroll
3333 */
3434import { ScrollView , RefreshControl , Gesture , GestureDetector } from 'react-native-gesture-handler'
35- import { View , NativeSyntheticEvent , NativeScrollEvent , LayoutChangeEvent , ViewStyle } from 'react-native'
35+ import { View , NativeSyntheticEvent , NativeScrollEvent , LayoutChangeEvent , ViewStyle , Animated as RNAnimated } from 'react-native'
3636import { isValidElement , Children , JSX , ReactNode , RefObject , useRef , useState , useEffect , forwardRef , useContext , useMemo , createElement } from 'react'
3737import Animated , { useAnimatedRef , useSharedValue , withTiming , useAnimatedStyle , runOnJS } from 'react-native-reanimated'
3838import { warn , hasOwn } from '@mpxjs/utils'
@@ -43,48 +43,49 @@ import { IntersectionObserverContext, ScrollViewContext } from './context'
4343import Portal from './mpx-portal'
4444
4545interface ScrollViewProps {
46- children ?: ReactNode
47- enhanced ?: boolean
48- bounces ?: boolean
49- style ?: ViewStyle
50- scrollEventThrottle ?: number
51- 'scroll-x' ?: boolean
52- 'scroll-y' ?: boolean
53- 'enable-back-to-top' ?: boolean
54- 'show-scrollbar' ?: boolean
55- 'paging-enabled' ?: boolean
56- 'upper-threshold' ?: number
57- 'lower-threshold' ?: number
58- 'scroll-with-animation' ?: boolean
59- 'refresher-triggered' ?: boolean
60- 'refresher-enabled' ?: boolean
61- 'refresher-default-style' ?: 'black' | 'white' | 'none'
62- 'refresher-background' ?: string
63- 'refresher-threshold' ?: number
64- 'scroll-top' ?: number
65- 'scroll-left' ?: number
66- 'enable-offset' ?: boolean
67- 'scroll-into-view' ?: string
68- 'enable-trigger-intersection-observer' ?: boolean
69- 'enable-var' ?: boolean
70- 'external-var-context' ?: Record < string , any >
71- 'parent-font-size' ?: number
72- 'parent-width' ?: number
73- 'parent-height' ?: number
74- 'wait-for' ?: Array < GestureHandler >
75- 'simultaneous-handlers' ?: Array < GestureHandler >
76- 'scroll-event-throttle' ?: number
77- bindscrolltoupper ?: ( event : NativeSyntheticEvent < NativeScrollEvent > ) => void
78- bindscrolltolower ?: ( event : NativeSyntheticEvent < NativeScrollEvent > ) => void
79- bindscroll ?: ( event : NativeSyntheticEvent < NativeScrollEvent > ) => void
80- bindrefresherrefresh ?: ( event : NativeSyntheticEvent < unknown > ) => void
81- binddragstart ?: ( event : NativeSyntheticEvent < DragEvent > ) => void
82- binddragging ?: ( event : NativeSyntheticEvent < DragEvent > ) => void
83- binddragend ?: ( event : NativeSyntheticEvent < DragEvent > ) => void
84- bindtouchstart ?: ( event : NativeSyntheticEvent < TouchEvent > ) => void
85- bindtouchmove ?: ( event : NativeSyntheticEvent < TouchEvent > ) => void
86- bindtouchend ?: ( event : NativeSyntheticEvent < TouchEvent > ) => void
87- bindscrollend ?: ( event : NativeSyntheticEvent < TouchEvent > ) => void
46+ children ?: ReactNode ;
47+ enhanced ?: boolean ;
48+ bounces ?: boolean ;
49+ style ?: ViewStyle ;
50+ 'scroll-x' ?: boolean ;
51+ 'scroll-y' ?: boolean ;
52+ 'enable-back-to-top' ?: boolean ;
53+ 'show-scrollbar' ?: boolean ;
54+ 'paging-enabled' ?: boolean ;
55+ 'upper-threshold' ?: number ;
56+ 'lower-threshold' ?: number ;
57+ 'scroll-with-animation' ?: boolean ;
58+ 'refresher-triggered' ?: boolean ;
59+ 'refresher-enabled' ?: boolean ;
60+ 'refresher-default-style' ?: 'black' | 'white' | 'none' ;
61+ 'refresher-background' ?: string ;
62+ 'refresher-threshold' ?: number ;
63+ 'scroll-top' ?: number ;
64+ 'scroll-left' ?: number ;
65+ 'enable-offset' ?: boolean ;
66+ 'scroll-into-view' ?: string ;
67+ 'enable-trigger-intersection-observer' ?: boolean ;
68+ 'enable-var' ?: boolean ;
69+ 'external-var-context' ?: Record < string , any > ;
70+ 'parent-font-size' ?: number ;
71+ 'parent-width' ?: number ;
72+ 'parent-height' ?: number ;
73+ 'enable-sticky' ?: boolean ;
74+ 'wait-for' ?: Array < GestureHandler > ;
75+ 'simultaneous-handlers' ?: Array < GestureHandler > ;
76+ 'scroll-event-throttle' ?:number ;
77+ 'scroll-into-view-offset' ?: number ;
78+ bindscrolltoupper ?: ( event : NativeSyntheticEvent < NativeScrollEvent > ) => void ;
79+ bindscrolltolower ?: ( event : NativeSyntheticEvent < NativeScrollEvent > ) => void ;
80+ bindscroll ?: ( event : NativeSyntheticEvent < NativeScrollEvent > ) => void ;
81+ bindrefresherrefresh ?: ( event : NativeSyntheticEvent < unknown > ) => void ;
82+ binddragstart ?: ( event : NativeSyntheticEvent < DragEvent > ) => void ;
83+ binddragging ?: ( event : NativeSyntheticEvent < DragEvent > ) => void ;
84+ binddragend ?: ( event : NativeSyntheticEvent < DragEvent > ) => void ;
85+ bindtouchstart ?: ( event : NativeSyntheticEvent < TouchEvent > ) => void ;
86+ bindtouchmove ?: ( event : NativeSyntheticEvent < TouchEvent > ) => void ;
87+ bindtouchend ?: ( event : NativeSyntheticEvent < TouchEvent > ) => void ;
88+ bindscrollend ?: ( event : NativeSyntheticEvent < TouchEvent > ) => void ;
8889 __selectRef ?: ( selector : string , nodeType : 'node' | 'component' , all ?: boolean ) => HandlerRef < any , any >
8990}
9091type ScrollAdditionalProps = {
@@ -109,6 +110,8 @@ type ScrollAdditionalProps = {
109110 onMomentumScrollEnd ?: ( event : NativeSyntheticEvent < NativeScrollEvent > ) => void
110111}
111112
113+ const AnimatedScrollView = RNAnimated . createAnimatedComponent ( ScrollView ) as React . ComponentType < any >
114+
112115const _ScrollView = forwardRef < HandlerRef < ScrollView & View , ScrollViewProps > , ScrollViewProps > ( ( scrollViewProps : ScrollViewProps = { } , ref ) : JSX . Element => {
113116 const { textProps, innerProps : props = { } } = splitProps ( scrollViewProps )
114117 const {
@@ -145,10 +148,14 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
145148 'parent-height' : parentHeight ,
146149 'simultaneous-handlers' : originSimultaneousHandlers ,
147150 'wait-for' : waitFor ,
151+ 'enable-sticky' : enableSticky ,
148152 'scroll-event-throttle' : scrollEventThrottle = 0 ,
153+ 'scroll-into-view-offset' : scrollIntoViewOffset = 0 ,
149154 __selectRef
150155 } = props
151156
157+ const scrollOffset = useRef ( new RNAnimated . Value ( 0 ) ) . current
158+
152159 const simultaneousHandlers = flatGesture ( originSimultaneousHandlers )
153160 const waitForHandlers = flatGesture ( waitFor )
154161
@@ -180,7 +187,7 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
180187 const initialTimeout = useRef < ReturnType < typeof setTimeout > | null > ( null )
181188 const intersectionObservers = useContext ( IntersectionObserverContext )
182189
183- const firstScrollIntoViewChange = useRef < boolean > ( false )
190+ const firstScrollIntoViewChange = useRef < boolean > ( true )
184191
185192 const refreshColor = {
186193 black : [ '#000' ] ,
@@ -213,19 +220,21 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
213220 pagingEnabled,
214221 fastDeceleration : false ,
215222 decelerationDisabled : false ,
216- scrollTo
223+ scrollTo,
224+ scrollIntoView : handleScrollIntoView
217225 } ,
218226 gestureRef : scrollViewRef
219227 } )
220228
229+ const { layoutRef, layoutStyle, layoutProps } = useLayout ( { props, hasSelfPercent, setWidth, setHeight, nodeRef : scrollViewRef , onLayout } )
230+
221231 const contextValue = useMemo ( ( ) => {
222232 return {
223- gestureRef : scrollViewRef
233+ gestureRef : scrollViewRef ,
234+ scrollOffset
224235 }
225236 } , [ ] )
226237
227- const { layoutRef, layoutStyle, layoutProps } = useLayout ( { props, hasSelfPercent, setWidth, setHeight, nodeRef : scrollViewRef , onLayout } )
228-
229238 const hasRefresherLayoutRef = useRef ( false )
230239
231240 // layout 完成前先隐藏,避免安卓闪烁问题
@@ -251,13 +260,15 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
251260
252261 useEffect ( ( ) => {
253262 if ( scrollIntoView && __selectRef ) {
254- if ( ! firstScrollIntoViewChange . current ) {
255- setTimeout ( handleScrollIntoView )
263+ if ( firstScrollIntoViewChange . current ) {
264+ setTimeout ( ( ) => {
265+ handleScrollIntoView ( scrollIntoView , { offset : scrollIntoViewOffset , animated : scrollWithAnimation } )
266+ } )
256267 } else {
257- handleScrollIntoView ( )
268+ handleScrollIntoView ( scrollIntoView , { offset : scrollIntoViewOffset , animated : scrollWithAnimation } )
258269 }
259270 }
260- firstScrollIntoViewChange . current = true
271+ firstScrollIntoViewChange . current = false
261272 } , [ scrollIntoView ] )
262273
263274 useEffect ( ( ) => {
@@ -280,14 +291,16 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
280291 scrollToOffset ( left , top , animated )
281292 }
282293
283- function handleScrollIntoView ( ) {
284- const refs = __selectRef ! ( `#${ scrollIntoView } ` , 'node' )
294+ function handleScrollIntoView ( selector = '' , { offset = 0 , animated = true } = { } ) {
295+ const refs = __selectRef ! ( `#${ selector } ` , 'node' )
285296 if ( ! refs ) return
286297 const { nodeRef } = refs . getNodeInstance ( )
287298 nodeRef . current ?. measureLayout (
288299 scrollViewRef . current ,
289300 ( left : number , top : number ) => {
290- scrollToOffset ( left , top )
301+ const adjustedLeft = scrollX ? left + offset : left
302+ const adjustedTop = scrollY ? top + offset : top
303+ scrollToOffset ( adjustedLeft , adjustedTop , animated )
291304 }
292305 )
293306 }
@@ -487,6 +500,16 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
487500 updateIntersection ( )
488501 }
489502
503+ const scrollHandler = RNAnimated . event (
504+ [ { nativeEvent : { contentOffset : { y : scrollOffset } } } ] ,
505+ {
506+ useNativeDriver : true ,
507+ listener : ( event : NativeSyntheticEvent < NativeScrollEvent > ) => {
508+ onScroll ( event )
509+ }
510+ }
511+ )
512+
490513 function onScrollDragStart ( e : NativeSyntheticEvent < NativeScrollEvent > ) {
491514 hasCallScrollToLower . current = false
492515 hasCallScrollToUpper . current = false
@@ -661,7 +684,7 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
661684 scrollEnabled : ! enableScroll ? false : ! ! ( scrollX || scrollY ) ,
662685 bounces : false ,
663686 ref : scrollViewRef ,
664- onScroll : onScroll ,
687+ onScroll : enableSticky ? scrollHandler : onScroll ,
665688 onContentSizeChange : onContentSizeChange ,
666689 bindtouchstart : ( ( enhanced && binddragstart ) || bindtouchstart ) && onScrollTouchStart ,
667690 bindtouchmove : ( ( enhanced && binddragging ) || bindtouchmove ) && onScrollTouchMove ,
@@ -716,11 +739,13 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
716739 'bindrefresherrefresh'
717740 ] , { layoutRef } )
718741
742+ const ScrollViewComponent = enableSticky ? AnimatedScrollView : ScrollView
743+
719744 const withRefresherScrollView = createElement (
720745 GestureDetector ,
721746 { gesture : panGesture } ,
722747 createElement (
723- ScrollView ,
748+ ScrollViewComponent ,
724749 innerProps ,
725750 createElement (
726751 Animated . View ,
@@ -748,8 +773,8 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
748773 )
749774
750775 const commonScrollView = createElement (
751- ScrollView ,
752- extendObject ( innerProps , {
776+ ScrollViewComponent ,
777+ extendObject ( { } , innerProps , {
753778 refreshControl : refresherEnabled
754779 ? createElement ( RefreshControl , extendObject ( {
755780 progressBackgroundColor : refresherBackground ,
0 commit comments