[new arch] Fix profile perf (#8320)
* change approach for keeping scrollviews synced * rm forwardRef from web too --------- Co-authored-by: Hailey <me@haileyok.com>
This commit is contained in:
+115
-107
@@ -1,16 +1,22 @@
|
||||
import React, {forwardRef, useCallback, useContext} from 'react'
|
||||
import {
|
||||
useCallback,
|
||||
useContext,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {DrawerGestureContext} from 'react-native-drawer-layout'
|
||||
import {Gesture, GestureDetector} from 'react-native-gesture-handler'
|
||||
import PagerView, {
|
||||
PagerViewOnPageScrollEventData,
|
||||
PagerViewOnPageSelectedEvent,
|
||||
PagerViewOnPageSelectedEventData,
|
||||
PageScrollStateChangedNativeEventData,
|
||||
type PagerViewOnPageScrollEventData,
|
||||
type PagerViewOnPageSelectedEvent,
|
||||
type PagerViewOnPageSelectedEventData,
|
||||
type PageScrollStateChangedNativeEventData,
|
||||
} from 'react-native-pager-view'
|
||||
import Animated, {
|
||||
runOnJS,
|
||||
SharedValue,
|
||||
type SharedValue,
|
||||
useEvent,
|
||||
useHandler,
|
||||
useSharedValue,
|
||||
@@ -36,8 +42,12 @@ export interface RenderTabBarFnProps {
|
||||
export type RenderTabBarFn = (props: RenderTabBarFnProps) => JSX.Element
|
||||
|
||||
interface Props {
|
||||
ref?: React.Ref<PagerRef>
|
||||
initialPage?: number
|
||||
renderTabBar: RenderTabBarFn
|
||||
// tab pressed, yet to scroll to page
|
||||
onTabPressed?: (index: number) => void
|
||||
// scroll settled
|
||||
onPageSelected?: (index: number) => void
|
||||
onPageScrollStateChanged?: (
|
||||
scrollState: 'idle' | 'dragging' | 'settling',
|
||||
@@ -47,114 +57,112 @@ interface Props {
|
||||
|
||||
const AnimatedPagerView = Animated.createAnimatedComponent(PagerView)
|
||||
|
||||
export const Pager = forwardRef<PagerRef, React.PropsWithChildren<Props>>(
|
||||
function PagerImpl(
|
||||
export function Pager({
|
||||
ref,
|
||||
children,
|
||||
initialPage = 0,
|
||||
renderTabBar,
|
||||
onPageSelected: parentOnPageSelected,
|
||||
onTabPressed: parentOnTabPressed,
|
||||
onPageScrollStateChanged: parentOnPageScrollStateChanged,
|
||||
testID,
|
||||
}: React.PropsWithChildren<Props>) {
|
||||
const [selectedPage, setSelectedPage] = useState(initialPage)
|
||||
const pagerView = useRef<PagerView>(null)
|
||||
|
||||
const [isIdle, setIsIdle] = useState(true)
|
||||
const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const canSwipeDrawer = selectedPage === 0 && isIdle
|
||||
setDrawerSwipeDisabled(!canSwipeDrawer)
|
||||
return () => {
|
||||
setDrawerSwipeDisabled(false)
|
||||
}
|
||||
}, [setDrawerSwipeDisabled, selectedPage, isIdle]),
|
||||
)
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
setPage: (index: number) => {
|
||||
pagerView.current?.setPage(index)
|
||||
},
|
||||
}))
|
||||
|
||||
const onPageSelectedJSThread = useCallback(
|
||||
(nextPosition: number) => {
|
||||
setSelectedPage(nextPosition)
|
||||
parentOnPageSelected?.(nextPosition)
|
||||
},
|
||||
[setSelectedPage, parentOnPageSelected],
|
||||
)
|
||||
|
||||
const onTabBarSelect = useCallback(
|
||||
(index: number) => {
|
||||
parentOnTabPressed?.(index)
|
||||
pagerView.current?.setPage(index)
|
||||
},
|
||||
[pagerView, parentOnTabPressed],
|
||||
)
|
||||
|
||||
const dragState = useSharedValue<'idle' | 'settling' | 'dragging'>('idle')
|
||||
const dragProgress = useSharedValue(selectedPage)
|
||||
const didInit = useSharedValue(false)
|
||||
const handlePageScroll = usePagerHandlers(
|
||||
{
|
||||
children,
|
||||
initialPage = 0,
|
||||
renderTabBar,
|
||||
onPageScrollStateChanged: parentOnPageScrollStateChanged,
|
||||
onPageSelected: parentOnPageSelected,
|
||||
testID,
|
||||
}: React.PropsWithChildren<Props>,
|
||||
ref,
|
||||
) {
|
||||
const [selectedPage, setSelectedPage] = React.useState(initialPage)
|
||||
const pagerView = React.useRef<PagerView>(null)
|
||||
|
||||
const [isIdle, setIsIdle] = React.useState(true)
|
||||
const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const canSwipeDrawer = selectedPage === 0 && isIdle
|
||||
setDrawerSwipeDisabled(!canSwipeDrawer)
|
||||
return () => {
|
||||
setDrawerSwipeDisabled(false)
|
||||
onPageScroll(e: PagerViewOnPageScrollEventData) {
|
||||
'worklet'
|
||||
if (didInit.get() === false) {
|
||||
// On iOS, there's a spurious scroll event with 0 position
|
||||
// even if a different page was supplied as the initial page.
|
||||
// Ignore it and wait for the first confirmed selection instead.
|
||||
return
|
||||
}
|
||||
}, [setDrawerSwipeDisabled, selectedPage, isIdle]),
|
||||
)
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
setPage: (index: number) => {
|
||||
pagerView.current?.setPage(index)
|
||||
dragProgress.set(e.offset + e.position)
|
||||
},
|
||||
}))
|
||||
|
||||
const onPageSelectedJSThread = React.useCallback(
|
||||
(nextPosition: number) => {
|
||||
setSelectedPage(nextPosition)
|
||||
parentOnPageSelected?.(nextPosition)
|
||||
onPageScrollStateChanged(e: PageScrollStateChangedNativeEventData) {
|
||||
'worklet'
|
||||
runOnJS(setIsIdle)(e.pageScrollState === 'idle')
|
||||
if (dragState.get() === 'idle' && e.pageScrollState === 'settling') {
|
||||
// This is a programmatic scroll on Android.
|
||||
// Stay "idle" to match iOS and avoid confusing downstream code.
|
||||
return
|
||||
}
|
||||
dragState.set(e.pageScrollState)
|
||||
parentOnPageScrollStateChanged?.(e.pageScrollState)
|
||||
},
|
||||
[setSelectedPage, parentOnPageSelected],
|
||||
)
|
||||
|
||||
const onTabBarSelect = React.useCallback(
|
||||
(index: number) => {
|
||||
pagerView.current?.setPage(index)
|
||||
onPageSelected(e: PagerViewOnPageSelectedEventData) {
|
||||
'worklet'
|
||||
didInit.set(true)
|
||||
runOnJS(onPageSelectedJSThread)(e.position)
|
||||
},
|
||||
[pagerView],
|
||||
)
|
||||
},
|
||||
[parentOnPageScrollStateChanged],
|
||||
)
|
||||
|
||||
const dragState = useSharedValue<'idle' | 'settling' | 'dragging'>('idle')
|
||||
const dragProgress = useSharedValue(selectedPage)
|
||||
const didInit = useSharedValue(false)
|
||||
const handlePageScroll = usePagerHandlers(
|
||||
{
|
||||
onPageScroll(e: PagerViewOnPageScrollEventData) {
|
||||
'worklet'
|
||||
if (didInit.get() === false) {
|
||||
// On iOS, there's a spurious scroll event with 0 position
|
||||
// even if a different page was supplied as the initial page.
|
||||
// Ignore it and wait for the first confirmed selection instead.
|
||||
return
|
||||
}
|
||||
dragProgress.set(e.offset + e.position)
|
||||
},
|
||||
onPageScrollStateChanged(e: PageScrollStateChangedNativeEventData) {
|
||||
'worklet'
|
||||
runOnJS(setIsIdle)(e.pageScrollState === 'idle')
|
||||
if (dragState.get() === 'idle' && e.pageScrollState === 'settling') {
|
||||
// This is a programmatic scroll on Android.
|
||||
// Stay "idle" to match iOS and avoid confusing downstream code.
|
||||
return
|
||||
}
|
||||
dragState.set(e.pageScrollState)
|
||||
parentOnPageScrollStateChanged?.(e.pageScrollState)
|
||||
},
|
||||
onPageSelected(e: PagerViewOnPageSelectedEventData) {
|
||||
'worklet'
|
||||
didInit.set(true)
|
||||
runOnJS(onPageSelectedJSThread)(e.position)
|
||||
},
|
||||
},
|
||||
[parentOnPageScrollStateChanged],
|
||||
)
|
||||
const drawerGesture = useContext(DrawerGestureContext) ?? Gesture.Native() // noop for web
|
||||
const nativeGesture =
|
||||
Gesture.Native().requireExternalGestureToFail(drawerGesture)
|
||||
|
||||
const drawerGesture = useContext(DrawerGestureContext) ?? Gesture.Native() // noop for web
|
||||
const nativeGesture =
|
||||
Gesture.Native().requireExternalGestureToFail(drawerGesture)
|
||||
|
||||
return (
|
||||
<View testID={testID} style={[a.flex_1, native(a.overflow_hidden)]}>
|
||||
{renderTabBar({
|
||||
selectedPage,
|
||||
onSelect: onTabBarSelect,
|
||||
dragProgress,
|
||||
dragState,
|
||||
})}
|
||||
<GestureDetector gesture={nativeGesture}>
|
||||
<AnimatedPagerView
|
||||
ref={pagerView}
|
||||
style={[a.flex_1]}
|
||||
initialPage={initialPage}
|
||||
onPageScroll={handlePageScroll}>
|
||||
{children}
|
||||
</AnimatedPagerView>
|
||||
</GestureDetector>
|
||||
</View>
|
||||
)
|
||||
},
|
||||
)
|
||||
return (
|
||||
<View testID={testID} style={[a.flex_1, native(a.overflow_hidden)]}>
|
||||
{renderTabBar({
|
||||
selectedPage,
|
||||
onSelect: onTabBarSelect,
|
||||
dragProgress,
|
||||
dragState,
|
||||
})}
|
||||
<GestureDetector gesture={nativeGesture}>
|
||||
<AnimatedPagerView
|
||||
ref={pagerView}
|
||||
style={[a.flex_1]}
|
||||
initialPage={initialPage}
|
||||
onPageScroll={handlePageScroll}>
|
||||
{children}
|
||||
</AnimatedPagerView>
|
||||
</GestureDetector>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function usePagerHandlers(
|
||||
handlers: {
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
Children,
|
||||
useCallback,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {flushSync} from 'react-dom'
|
||||
|
||||
import {s} from '#/lib/styles'
|
||||
import {atoms as a} from '#/alf'
|
||||
|
||||
export interface PagerRef {
|
||||
setPage: (index: number) => void
|
||||
}
|
||||
|
||||
export interface RenderTabBarFnProps {
|
||||
selectedPage: number
|
||||
@@ -12,30 +23,30 @@ export interface RenderTabBarFnProps {
|
||||
export type RenderTabBarFn = (props: RenderTabBarFnProps) => JSX.Element
|
||||
|
||||
interface Props {
|
||||
ref?: React.Ref<PagerRef>
|
||||
initialPage?: number
|
||||
renderTabBar: RenderTabBarFn
|
||||
onPageSelected?: (index: number) => void
|
||||
}
|
||||
export const Pager = React.forwardRef(function PagerImpl(
|
||||
{
|
||||
children,
|
||||
initialPage = 0,
|
||||
renderTabBar,
|
||||
onPageSelected,
|
||||
}: React.PropsWithChildren<Props>,
|
||||
ref,
|
||||
) {
|
||||
const [selectedPage, setSelectedPage] = React.useState(initialPage)
|
||||
const scrollYs = React.useRef<Array<number | null>>([])
|
||||
const anchorRef = React.useRef(null)
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
export function Pager({
|
||||
ref,
|
||||
children,
|
||||
initialPage = 0,
|
||||
renderTabBar,
|
||||
onPageSelected,
|
||||
}: React.PropsWithChildren<Props>) {
|
||||
const [selectedPage, setSelectedPage] = useState(initialPage)
|
||||
const scrollYs = useRef<Array<number | null>>([])
|
||||
const anchorRef = useRef(null)
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
setPage: (index: number) => {
|
||||
onTabBarSelect(index)
|
||||
},
|
||||
}))
|
||||
|
||||
const onTabBarSelect = React.useCallback(
|
||||
const onTabBarSelect = useCallback(
|
||||
(index: number) => {
|
||||
const scrollY = window.scrollY
|
||||
// We want to determine if the tabbar is already "sticking" at the top (in which
|
||||
@@ -75,11 +86,13 @@ export const Pager = React.forwardRef(function PagerImpl(
|
||||
tabBarAnchor: <View ref={anchorRef} />,
|
||||
onSelect: e => onTabBarSelect(e),
|
||||
})}
|
||||
{React.Children.map(children, (child, i) => (
|
||||
<View style={selectedPage === i ? s.flex1 : s.hidden} key={`page-${i}`}>
|
||||
{Children.map(children, (child, i) => (
|
||||
<View
|
||||
style={selectedPage === i ? a.flex_1 : a.hidden}
|
||||
key={`page-${i}`}>
|
||||
{child}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import * as React from 'react'
|
||||
import {memo, useCallback, useEffect, useRef, useState} from 'react'
|
||||
import {
|
||||
LayoutChangeEvent,
|
||||
NativeScrollEvent,
|
||||
ScrollView,
|
||||
type LayoutChangeEvent,
|
||||
type NativeScrollEvent,
|
||||
type ScrollView,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import Animated, {
|
||||
AnimatedRef,
|
||||
runOnJS,
|
||||
type AnimatedRef,
|
||||
runOnUI,
|
||||
scrollTo,
|
||||
SharedValue,
|
||||
type SharedValue,
|
||||
useAnimatedRef,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
@@ -20,9 +19,13 @@ import Animated, {
|
||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
import {ScrollProvider} from '#/lib/ScrollContext'
|
||||
import {isIOS} from '#/platform/detection'
|
||||
import {Pager, PagerRef, RenderTabBarFnProps} from '#/view/com/pager/Pager'
|
||||
import {
|
||||
Pager,
|
||||
type PagerRef,
|
||||
type RenderTabBarFnProps,
|
||||
} from '#/view/com/pager/Pager'
|
||||
import {useTheme} from '#/alf'
|
||||
import {ListMethods} from '../util/List'
|
||||
import {type ListMethods} from '../util/List'
|
||||
import {PagerHeaderProvider} from './PagerHeaderContext'
|
||||
import {TabBar} from './TabBar'
|
||||
|
||||
@@ -33,6 +36,7 @@ export interface PagerWithHeaderChildParams {
|
||||
}
|
||||
|
||||
export interface PagerWithHeaderProps {
|
||||
ref?: React.Ref<PagerRef>
|
||||
testID?: string
|
||||
children:
|
||||
| (((props: PagerWithHeaderChildParams) => JSX.Element) | null)[]
|
||||
@@ -49,97 +53,94 @@ export interface PagerWithHeaderProps {
|
||||
onCurrentPageSelected?: (index: number) => void
|
||||
allowHeaderOverScroll?: boolean
|
||||
}
|
||||
export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
||||
function PageWithHeaderImpl(
|
||||
{
|
||||
children,
|
||||
testID,
|
||||
export function PagerWithHeader({
|
||||
ref,
|
||||
children,
|
||||
testID,
|
||||
items,
|
||||
isHeaderReady,
|
||||
renderHeader,
|
||||
initialPage,
|
||||
onPageSelected,
|
||||
onCurrentPageSelected,
|
||||
allowHeaderOverScroll,
|
||||
}: PagerWithHeaderProps) {
|
||||
const [currentPage, setCurrentPage] = useState(0)
|
||||
const [tabBarHeight, setTabBarHeight] = useState(0)
|
||||
const [headerOnlyHeight, setHeaderOnlyHeight] = useState(0)
|
||||
const scrollY = useSharedValue(0)
|
||||
const headerHeight = headerOnlyHeight + tabBarHeight
|
||||
|
||||
// capture the header bar sizing
|
||||
const onTabBarLayout = useNonReactiveCallback((evt: LayoutChangeEvent) => {
|
||||
const height = evt.nativeEvent.layout.height
|
||||
if (height > 0) {
|
||||
// The rounding is necessary to prevent jumps on iOS
|
||||
setTabBarHeight(Math.round(height * 2) / 2)
|
||||
}
|
||||
})
|
||||
const onHeaderOnlyLayout = useNonReactiveCallback((height: number) => {
|
||||
if (height > 0) {
|
||||
// The rounding is necessary to prevent jumps on iOS
|
||||
setHeaderOnlyHeight(Math.round(height * 2) / 2)
|
||||
}
|
||||
})
|
||||
|
||||
const renderTabBar = useCallback(
|
||||
(props: RenderTabBarFnProps) => {
|
||||
return (
|
||||
<PagerHeaderProvider scrollY={scrollY} headerHeight={headerOnlyHeight}>
|
||||
<PagerTabBar
|
||||
headerOnlyHeight={headerOnlyHeight}
|
||||
items={items}
|
||||
isHeaderReady={isHeaderReady}
|
||||
renderHeader={renderHeader}
|
||||
currentPage={currentPage}
|
||||
onCurrentPageSelected={onCurrentPageSelected}
|
||||
onTabBarLayout={onTabBarLayout}
|
||||
onHeaderOnlyLayout={onHeaderOnlyLayout}
|
||||
onSelect={props.onSelect}
|
||||
scrollY={scrollY}
|
||||
testID={testID}
|
||||
allowHeaderOverScroll={allowHeaderOverScroll}
|
||||
dragProgress={props.dragProgress}
|
||||
dragState={props.dragState}
|
||||
/>
|
||||
</PagerHeaderProvider>
|
||||
)
|
||||
},
|
||||
[
|
||||
headerOnlyHeight,
|
||||
items,
|
||||
isHeaderReady,
|
||||
renderHeader,
|
||||
initialPage,
|
||||
onPageSelected,
|
||||
currentPage,
|
||||
onCurrentPageSelected,
|
||||
onTabBarLayout,
|
||||
onHeaderOnlyLayout,
|
||||
scrollY,
|
||||
testID,
|
||||
allowHeaderOverScroll,
|
||||
}: PagerWithHeaderProps,
|
||||
ref,
|
||||
) {
|
||||
const [currentPage, setCurrentPage] = React.useState(0)
|
||||
const [tabBarHeight, setTabBarHeight] = React.useState(0)
|
||||
const [headerOnlyHeight, setHeaderOnlyHeight] = React.useState(0)
|
||||
const scrollY = useSharedValue(0)
|
||||
const headerHeight = headerOnlyHeight + tabBarHeight
|
||||
],
|
||||
)
|
||||
|
||||
// capture the header bar sizing
|
||||
const onTabBarLayout = useNonReactiveCallback((evt: LayoutChangeEvent) => {
|
||||
const height = evt.nativeEvent.layout.height
|
||||
if (height > 0) {
|
||||
// The rounding is necessary to prevent jumps on iOS
|
||||
setTabBarHeight(Math.round(height * 2) / 2)
|
||||
}
|
||||
})
|
||||
const onHeaderOnlyLayout = useNonReactiveCallback((height: number) => {
|
||||
if (height > 0) {
|
||||
// The rounding is necessary to prevent jumps on iOS
|
||||
setHeaderOnlyHeight(Math.round(height * 2) / 2)
|
||||
}
|
||||
})
|
||||
const scrollRefs = useSharedValue<Array<AnimatedRef<any> | null>>([])
|
||||
const registerRef = useCallback(
|
||||
(scrollRef: AnimatedRef<any> | null, atIndex: number) => {
|
||||
scrollRefs.modify(refs => {
|
||||
'worklet'
|
||||
refs[atIndex] = scrollRef
|
||||
return refs
|
||||
})
|
||||
},
|
||||
[scrollRefs],
|
||||
)
|
||||
|
||||
const renderTabBar = React.useCallback(
|
||||
(props: RenderTabBarFnProps) => {
|
||||
return (
|
||||
<PagerHeaderProvider
|
||||
scrollY={scrollY}
|
||||
headerHeight={headerOnlyHeight}>
|
||||
<PagerTabBar
|
||||
headerOnlyHeight={headerOnlyHeight}
|
||||
items={items}
|
||||
isHeaderReady={isHeaderReady}
|
||||
renderHeader={renderHeader}
|
||||
currentPage={currentPage}
|
||||
onCurrentPageSelected={onCurrentPageSelected}
|
||||
onTabBarLayout={onTabBarLayout}
|
||||
onHeaderOnlyLayout={onHeaderOnlyLayout}
|
||||
onSelect={props.onSelect}
|
||||
scrollY={scrollY}
|
||||
testID={testID}
|
||||
allowHeaderOverScroll={allowHeaderOverScroll}
|
||||
dragProgress={props.dragProgress}
|
||||
dragState={props.dragState}
|
||||
/>
|
||||
</PagerHeaderProvider>
|
||||
)
|
||||
},
|
||||
[
|
||||
headerOnlyHeight,
|
||||
items,
|
||||
isHeaderReady,
|
||||
renderHeader,
|
||||
currentPage,
|
||||
onCurrentPageSelected,
|
||||
onTabBarLayout,
|
||||
onHeaderOnlyLayout,
|
||||
scrollY,
|
||||
testID,
|
||||
allowHeaderOverScroll,
|
||||
],
|
||||
)
|
||||
|
||||
const scrollRefs = useSharedValue<Array<AnimatedRef<any> | null>>([])
|
||||
const registerRef = React.useCallback(
|
||||
(scrollRef: AnimatedRef<any> | null, atIndex: number) => {
|
||||
scrollRefs.modify(refs => {
|
||||
'worklet'
|
||||
refs[atIndex] = scrollRef
|
||||
return refs
|
||||
})
|
||||
},
|
||||
[scrollRefs],
|
||||
)
|
||||
|
||||
const lastForcedScrollY = useSharedValue(0)
|
||||
const adjustScrollForOtherPages = () => {
|
||||
const lastForcedScrollY = useSharedValue(0)
|
||||
const adjustScrollForOtherPages = useCallback(
|
||||
(scrollState: 'idle' | 'dragging' | 'settling') => {
|
||||
'worklet'
|
||||
if (scrollState !== 'dragging') return
|
||||
const currentScrollY = scrollY.get()
|
||||
const forcedScrollY = Math.min(currentScrollY, headerOnlyHeight)
|
||||
if (lastForcedScrollY.get() !== forcedScrollY) {
|
||||
@@ -152,75 +153,69 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[currentPage, headerOnlyHeight, lastForcedScrollY, scrollRefs, scrollY],
|
||||
)
|
||||
|
||||
const throttleTimeout = React.useRef<ReturnType<typeof setTimeout> | null>(
|
||||
null,
|
||||
)
|
||||
const queueThrottledOnScroll = useNonReactiveCallback(() => {
|
||||
if (!throttleTimeout.current) {
|
||||
throttleTimeout.current = setTimeout(() => {
|
||||
throttleTimeout.current = null
|
||||
runOnUI(adjustScrollForOtherPages)()
|
||||
}, 80 /* Sync often enough you're unlikely to catch it unsynced */)
|
||||
const onScrollWorklet = useCallback(
|
||||
(e: NativeScrollEvent) => {
|
||||
'worklet'
|
||||
const nextScrollY = e.contentOffset.y
|
||||
// HACK: onScroll is reporting some strange values on load (negative header height).
|
||||
// Highly improbable that you'd be overscrolled by over 400px -
|
||||
// in fact, I actually can't do it, so let's just ignore those. -sfn
|
||||
const isPossiblyInvalid =
|
||||
headerHeight > 0 && Math.round(nextScrollY * 2) / 2 === -headerHeight
|
||||
if (!isPossiblyInvalid) {
|
||||
scrollY.set(nextScrollY)
|
||||
}
|
||||
})
|
||||
},
|
||||
[scrollY, headerHeight],
|
||||
)
|
||||
|
||||
const onScrollWorklet = React.useCallback(
|
||||
(e: NativeScrollEvent) => {
|
||||
'worklet'
|
||||
const nextScrollY = e.contentOffset.y
|
||||
// HACK: onScroll is reporting some strange values on load (negative header height).
|
||||
// Highly improbable that you'd be overscrolled by over 400px -
|
||||
// in fact, I actually can't do it, so let's just ignore those. -sfn
|
||||
const isPossiblyInvalid =
|
||||
headerHeight > 0 && Math.round(nextScrollY * 2) / 2 === -headerHeight
|
||||
if (!isPossiblyInvalid) {
|
||||
scrollY.set(nextScrollY)
|
||||
runOnJS(queueThrottledOnScroll)()
|
||||
}
|
||||
},
|
||||
[scrollY, queueThrottledOnScroll, headerHeight],
|
||||
)
|
||||
const onPageSelectedInner = useCallback(
|
||||
(index: number) => {
|
||||
setCurrentPage(index)
|
||||
onPageSelected?.(index)
|
||||
},
|
||||
[onPageSelected, setCurrentPage],
|
||||
)
|
||||
|
||||
const onPageSelectedInner = React.useCallback(
|
||||
(index: number) => {
|
||||
setCurrentPage(index)
|
||||
onPageSelected?.(index)
|
||||
},
|
||||
[onPageSelected, setCurrentPage],
|
||||
)
|
||||
const onTabPressed = useCallback(() => {
|
||||
runOnUI(adjustScrollForOtherPages)('dragging')
|
||||
}, [adjustScrollForOtherPages])
|
||||
|
||||
return (
|
||||
<Pager
|
||||
ref={ref}
|
||||
testID={testID}
|
||||
initialPage={initialPage}
|
||||
onPageSelected={onPageSelectedInner}
|
||||
renderTabBar={renderTabBar}>
|
||||
{toArray(children)
|
||||
.filter(Boolean)
|
||||
.map((child, i) => {
|
||||
const isReady =
|
||||
isHeaderReady && headerOnlyHeight > 0 && tabBarHeight > 0
|
||||
return (
|
||||
<View key={i} collapsable={false}>
|
||||
<PagerItem
|
||||
headerHeight={headerHeight}
|
||||
index={i}
|
||||
isReady={isReady}
|
||||
isFocused={i === currentPage}
|
||||
onScrollWorklet={i === currentPage ? onScrollWorklet : noop}
|
||||
registerRef={registerRef}
|
||||
renderTab={child}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
})}
|
||||
</Pager>
|
||||
)
|
||||
},
|
||||
)
|
||||
return (
|
||||
<Pager
|
||||
ref={ref}
|
||||
testID={testID}
|
||||
initialPage={initialPage}
|
||||
onTabPressed={onTabPressed}
|
||||
onPageSelected={onPageSelectedInner}
|
||||
renderTabBar={renderTabBar}
|
||||
onPageScrollStateChanged={adjustScrollForOtherPages}>
|
||||
{toArray(children)
|
||||
.filter(Boolean)
|
||||
.map((child, i) => {
|
||||
const isReady =
|
||||
isHeaderReady && headerOnlyHeight > 0 && tabBarHeight > 0
|
||||
return (
|
||||
<View key={i} collapsable={false}>
|
||||
<PagerItem
|
||||
headerHeight={headerHeight}
|
||||
index={i}
|
||||
isReady={isReady}
|
||||
isFocused={i === currentPage}
|
||||
onScrollWorklet={i === currentPage ? onScrollWorklet : noop}
|
||||
registerRef={registerRef}
|
||||
renderTab={child}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
})}
|
||||
</Pager>
|
||||
)
|
||||
}
|
||||
|
||||
let PagerTabBar = ({
|
||||
currentPage,
|
||||
@@ -258,7 +253,7 @@ let PagerTabBar = ({
|
||||
dragState: SharedValue<'idle' | 'dragging' | 'settling'>
|
||||
}): React.ReactNode => {
|
||||
const t = useTheme()
|
||||
const [minimumHeaderHeight, setMinimumHeaderHeight] = React.useState(0)
|
||||
const [minimumHeaderHeight, setMinimumHeaderHeight] = useState(0)
|
||||
const headerTransform = useAnimatedStyle(() => {
|
||||
const translateY =
|
||||
Math.min(
|
||||
@@ -275,7 +270,7 @@ let PagerTabBar = ({
|
||||
],
|
||||
}
|
||||
})
|
||||
const headerRef = React.useRef(null)
|
||||
const headerRef = useRef(null)
|
||||
return (
|
||||
<Animated.View
|
||||
pointerEvents={isIOS ? 'auto' : 'box-none'}
|
||||
@@ -327,7 +322,7 @@ let PagerTabBar = ({
|
||||
</Animated.View>
|
||||
)
|
||||
}
|
||||
PagerTabBar = React.memo(PagerTabBar)
|
||||
PagerTabBar = memo(PagerTabBar)
|
||||
|
||||
function PagerItem({
|
||||
headerHeight,
|
||||
@@ -348,7 +343,7 @@ function PagerItem({
|
||||
}) {
|
||||
const scrollElRef = useAnimatedRef()
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
registerRef(scrollElRef, index)
|
||||
return () => {
|
||||
registerRef(null, index)
|
||||
|
||||
Reference in New Issue
Block a user