Track offset relative to current page

This commit is contained in:
Dan Abramov
2024-11-29 19:27:20 +00:00
parent 7732a7b8f3
commit a26a84eb60
+38 -12
View File
@@ -3,9 +3,11 @@ import {View} from 'react-native'
import PagerView, { import PagerView, {
PagerViewOnPageScrollEventData, PagerViewOnPageScrollEventData,
PagerViewOnPageSelectedEvent, PagerViewOnPageSelectedEvent,
PagerViewOnPageSelectedEventData,
PageScrollStateChangedNativeEventData, PageScrollStateChangedNativeEventData,
} from 'react-native-pager-view' } from 'react-native-pager-view'
import Animated, { import Animated, {
runOnJS,
useEvent, useEvent,
useHandler, useHandler,
useSharedValue, useSharedValue,
@@ -45,14 +47,13 @@ export const Pager = forwardRef<PagerRef, React.PropsWithChildren<Props>>(
initialPage = 0, initialPage = 0,
renderTabBar, renderTabBar,
onPageScrollStateChanged: parentOnPageScrollStateChanged, onPageScrollStateChanged: parentOnPageScrollStateChanged,
onPageSelected, onPageSelected: parentOnPageSelected,
testID, testID,
}: React.PropsWithChildren<Props>, }: React.PropsWithChildren<Props>,
ref, ref,
) { ) {
const [selectedPage, setSelectedPage] = React.useState(0) const [selectedPage, setSelectedPage] = React.useState(0)
const pagerView = React.useRef<PagerView>(null) const pagerView = React.useRef<PagerView>(null)
const dragProgress = useSharedValue(selectedPage)
React.useImperativeHandle(ref, () => ({ React.useImperativeHandle(ref, () => ({
setPage: (index: number) => { setPage: (index: number) => {
@@ -60,12 +61,12 @@ export const Pager = forwardRef<PagerRef, React.PropsWithChildren<Props>>(
}, },
})) }))
const onPageSelectedInner = React.useCallback( const onPageSelectedJSThread = React.useCallback(
(e: PageSelectedEvent) => { (nextPosition: number) => {
setSelectedPage(e.nativeEvent.position) setSelectedPage(nextPosition)
onPageSelected?.(e.nativeEvent.position) parentOnPageSelected?.(nextPosition)
}, },
[setSelectedPage, onPageSelected], [setSelectedPage, parentOnPageSelected],
) )
const onTabBarSelect = React.useCallback( const onTabBarSelect = React.useCallback(
@@ -75,17 +76,31 @@ export const Pager = forwardRef<PagerRef, React.PropsWithChildren<Props>>(
[pagerView], [pagerView],
) )
const dragPage = useSharedValue(selectedPage)
const dragProgress = useSharedValue(0)
const dragState = useSharedValue<'idle' | 'settling' | 'dragging'>('idle')
const handlePageScroll = usePagerHandlers( const handlePageScroll = usePagerHandlers(
{ {
onPageScroll(e: PagerViewOnPageScrollEventData) { onPageScroll(e: PagerViewOnPageScrollEventData) {
'worklet' 'worklet'
const progress = e.offset + e.position let {position, offset} = e
dragProgress.value = progress if (position === dragPage.value) {
dragProgress.set(offset)
} else {
const offsetFromPage = position + offset - dragPage.value
dragProgress.set(offsetFromPage)
}
}, },
onPageScrollStateChanged(e: PageScrollStateChangedNativeEventData) { onPageScrollStateChanged(e: PageScrollStateChangedNativeEventData) {
'worklet' 'worklet'
dragState.set(e.pageScrollState)
parentOnPageScrollStateChanged?.(e.pageScrollState) parentOnPageScrollStateChanged?.(e.pageScrollState)
}, },
onPageSelected(e: PagerViewOnPageSelectedEventData) {
'worklet'
dragPage.set(e.position)
runOnJS(onPageSelectedJSThread)(e.position)
},
}, },
[parentOnPageScrollStateChanged], [parentOnPageScrollStateChanged],
) )
@@ -95,12 +110,16 @@ export const Pager = forwardRef<PagerRef, React.PropsWithChildren<Props>>(
{renderTabBar({ {renderTabBar({
selectedPage, selectedPage,
onSelect: onTabBarSelect, onSelect: onTabBarSelect,
dragGesture: {
dragPage,
dragProgress,
dragState,
},
})} })}
<AnimatedPagerView <AnimatedPagerView
ref={pagerView} ref={pagerView}
style={[a.flex_1]} style={[a.flex_1]}
initialPage={initialPage} initialPage={initialPage}
onPageSelected={onPageSelectedInner}
onPageScroll={handlePageScroll}> onPageScroll={handlePageScroll}>
{children} {children}
</AnimatedPagerView> </AnimatedPagerView>
@@ -113,21 +132,28 @@ function usePagerHandlers(
handlers: { handlers: {
onPageScroll: (e: PagerViewOnPageScrollEventData) => void onPageScroll: (e: PagerViewOnPageScrollEventData) => void
onPageScrollStateChanged: (e: PageScrollStateChangedNativeEventData) => void onPageScrollStateChanged: (e: PageScrollStateChangedNativeEventData) => void
onPageSelected: (e: PagerViewOnPageSelectedEventData) => void
}, },
dependencies: unknown[], dependencies: unknown[],
) { ) {
const {doDependenciesDiffer} = useHandler(handlers as any, dependencies) const {doDependenciesDiffer} = useHandler(handlers as any, dependencies)
const subscribeForEvents = ['onPageScroll', 'onPageScrollStateChanged'] const subscribeForEvents = [
'onPageScroll',
'onPageScrollStateChanged',
'onPageSelected',
]
return useEvent( return useEvent(
event => { event => {
'worklet' 'worklet'
const {onPageScroll, onPageScrollStateChanged} = handlers const {onPageScroll, onPageScrollStateChanged, onPageSelected} = handlers
if (event.eventName.endsWith('onPageScroll')) { if (event.eventName.endsWith('onPageScroll')) {
onPageScroll(event as any as PagerViewOnPageScrollEventData) onPageScroll(event as any as PagerViewOnPageScrollEventData)
} else if (event.eventName.endsWith('onPageScrollStateChanged')) { } else if (event.eventName.endsWith('onPageScrollStateChanged')) {
onPageScrollStateChanged( onPageScrollStateChanged(
event as any as PageScrollStateChangedNativeEventData, event as any as PageScrollStateChangedNativeEventData,
) )
} else if (event.eventName.endsWith('onPageSelected')) {
onPageSelected(event as any as PagerViewOnPageSelectedEventData)
} }
}, },
subscribeForEvents, subscribeForEvents,