Sync scroll to swipe properly

This commit is contained in:
Dan Abramov
2024-11-29 23:44:46 +00:00
parent 6d134229d6
commit 505e3adc77
2 changed files with 24 additions and 26 deletions
+18 -7
View File
@@ -76,18 +76,19 @@ export const Pager = forwardRef<PagerRef, React.PropsWithChildren<Props>>(
[pagerView], [pagerView],
) )
const pendingPage = useSharedValue(selectedPage)
const dragPage = useSharedValue(selectedPage) const dragPage = useSharedValue(selectedPage)
const dragProgress = useSharedValue(0) const dragProgress = useSharedValue(0)
const dragState = useSharedValue<'idle' | 'settling' | 'dragging'>('idle') const dragState = useSharedValue<'idle' | 'settling' | 'dragging'>('idle')
const handlePageScroll = usePagerHandlers( const handlePageScroll = usePagerHandlers(
// These events don't fire exactly the same way on Android and iOS.
// In these handlers we normalize the behavior to have consistent output values.
{ {
onPageScroll(e: PagerViewOnPageScrollEventData) { onPageScroll(e: PagerViewOnPageScrollEventData) {
'worklet' 'worklet'
let {position, offset} = e let {position, offset} = e
if (position === dragPage.value) { if (offset !== 0) {
dragProgress.set(offset) const offsetFromPage = offset + (position - dragPage.value)
} else {
const offsetFromPage = position + offset - dragPage.value
dragProgress.set(offsetFromPage) dragProgress.set(offsetFromPage)
} }
}, },
@@ -95,12 +96,22 @@ export const Pager = forwardRef<PagerRef, React.PropsWithChildren<Props>>(
'worklet' 'worklet'
dragState.set(e.pageScrollState) dragState.set(e.pageScrollState)
parentOnPageScrollStateChanged?.(e.pageScrollState) parentOnPageScrollStateChanged?.(e.pageScrollState)
if (e.pageScrollState === 'idle') {
const page = pendingPage.get()
dragPage.set(page)
dragProgress.set(0)
runOnJS(onPageSelectedJSThread)(page)
}
}, },
onPageSelected(e: PagerViewOnPageSelectedEventData) { onPageSelected(e: PagerViewOnPageSelectedEventData) {
'worklet' 'worklet'
dragPage.set(e.position) pendingPage.set(e.position)
dragProgress.set(0) if (dragState.value === 'idle') {
runOnJS(onPageSelectedJSThread)(e.position) const page = e.position
dragPage.set(page)
dragProgress.set(0)
runOnJS(onPageSelectedJSThread)(e.position)
}
}, },
}, },
[parentOnPageScrollStateChanged], [parentOnPageScrollStateChanged],
+6 -19
View File
@@ -4,7 +4,6 @@ import Animated, {
scrollTo, scrollTo,
useAnimatedReaction, useAnimatedReaction,
useAnimatedRef, useAnimatedRef,
useAnimatedStyle,
useSharedValue, useSharedValue,
} from 'react-native-reanimated' } from 'react-native-reanimated'
@@ -47,24 +46,17 @@ export function TabBar({
const {dragPage, dragProgress} = dragGesture const {dragPage, dragProgress} = dragGesture
useAnimatedReaction( useAnimatedReaction(
() => dragPage.get(), () => dragPage.get() + dragProgress.get(),
(page, prevPage) => { (next, prev) => {
if (page !== prevPage) { if (next !== prev) {
const offsetPerPage = contentSize.get() - containerSize.get() const offsetPerPage = contentSize.get() - containerSize.get()
const offset = (dragPage.get() / (itemsLength - 1)) * offsetPerPage const offset = (next / (itemsLength - 1)) * offsetPerPage
scrollTo(scrollElRef, offset, 0, false) scrollTo(scrollElRef, offset, 0, false)
return
} }
}, },
) )
const contentStyle = useAnimatedStyle(() => {
const offsetPerPage = contentSize.get() - containerSize.get()
const offset = (dragProgress.get() / (itemsLength - 1)) * offsetPerPage
return {
transform: [{translateX: -offset}],
}
})
return ( return (
<View <View
testID={testID} testID={testID}
@@ -83,12 +75,7 @@ export function TabBar({
onLayout={e => { onLayout={e => {
contentSize.set(e.nativeEvent.layout.width) contentSize.set(e.nativeEvent.layout.width)
}} }}
style={[ style={{flexDirection: 'row'}}>
contentStyle,
{
flexDirection: 'row',
},
]}>
{items.map((item, i) => { {items.map((item, i) => {
return ( return (
<TabBarItem <TabBarItem