Force scroll sync for inactive tabs
This commit is contained in:
@@ -6,6 +6,7 @@ import Animated, {
|
|||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
useSharedValue,
|
useSharedValue,
|
||||||
runOnJS,
|
runOnJS,
|
||||||
|
scrollTo,
|
||||||
useAnimatedRef,
|
useAnimatedRef,
|
||||||
} from 'react-native-reanimated'
|
} from 'react-native-reanimated'
|
||||||
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
|
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
|
||||||
@@ -13,8 +14,6 @@ import {TabBar} from './TabBar'
|
|||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
|
import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
|
||||||
|
|
||||||
const SCROLLED_DOWN_LIMIT = 200
|
|
||||||
|
|
||||||
interface PagerWithHeaderChildParams {
|
interface PagerWithHeaderChildParams {
|
||||||
headerHeight: number
|
headerHeight: number
|
||||||
onScroll: OnScrollCb
|
onScroll: OnScrollCb
|
||||||
@@ -50,21 +49,13 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
|||||||
) {
|
) {
|
||||||
const {isMobile} = useWebMediaQueries()
|
const {isMobile} = useWebMediaQueries()
|
||||||
const [currentPage, setCurrentPage] = React.useState(0)
|
const [currentPage, setCurrentPage] = React.useState(0)
|
||||||
const scrollY = useSharedValue(0)
|
const clampedScrollY = useSharedValue(0)
|
||||||
const [tabBarHeight, setTabBarHeight] = React.useState(0)
|
const [tabBarHeight, setTabBarHeight] = React.useState(0)
|
||||||
const [headerOnlyHeight, setHeaderOnlyHeight] = React.useState(0)
|
const [headerOnlyHeight, setHeaderOnlyHeight] = React.useState(0)
|
||||||
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
|
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
|
||||||
|
|
||||||
const headerHeight = headerOnlyHeight + tabBarHeight
|
const headerHeight = headerOnlyHeight + tabBarHeight
|
||||||
|
|
||||||
function onScrollUpdate(v: number) {
|
|
||||||
setIsScrolledDown(v > SCROLLED_DOWN_LIMIT)
|
|
||||||
}
|
|
||||||
useAnimatedReaction(
|
|
||||||
() => scrollY.value,
|
|
||||||
v => runOnJS(onScrollUpdate)(v),
|
|
||||||
)
|
|
||||||
|
|
||||||
// capture the header bar sizing
|
// capture the header bar sizing
|
||||||
const onTabBarLayout = React.useCallback(
|
const onTabBarLayout = React.useCallback(
|
||||||
(evt: LayoutChangeEvent) => {
|
(evt: LayoutChangeEvent) => {
|
||||||
@@ -84,14 +75,11 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
|||||||
() => ({
|
() => ({
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
translateY: Math.min(
|
translateY: Math.min(-clampedScrollY.value, 0),
|
||||||
Math.min(scrollY.value, headerOnlyHeight) * -1,
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
[scrollY, headerHeight, tabBarHeight],
|
[clampedScrollY, headerHeight, tabBarHeight],
|
||||||
)
|
)
|
||||||
const renderTabBar = React.useCallback(
|
const renderTabBar = React.useCallback(
|
||||||
(props: RenderTabBarFnProps) => {
|
(props: RenderTabBarFnProps) => {
|
||||||
@@ -136,9 +124,17 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
|||||||
// props to pass into children render functions
|
// props to pass into children render functions
|
||||||
const onScroll = useAnimatedScrollHandler({
|
const onScroll = useAnimatedScrollHandler({
|
||||||
onScroll(e) {
|
onScroll(e) {
|
||||||
scrollY.value = e.contentOffset.y
|
clampedScrollY.value = Math.min(e.contentOffset.y, headerOnlyHeight)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
useAnimatedReaction(
|
||||||
|
() => clampedScrollY.value === headerOnlyHeight,
|
||||||
|
(nextIsScrolledDown, prevIsScrolledDown) => {
|
||||||
|
if (nextIsScrolledDown !== prevIsScrolledDown) {
|
||||||
|
runOnJS(setIsScrolledDown)(nextIsScrolledDown)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const onPageSelectedInner = React.useCallback(
|
const onPageSelectedInner = React.useCallback(
|
||||||
(index: number) => {
|
(index: number) => {
|
||||||
@@ -170,6 +166,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
|||||||
headerHeight={headerHeight}
|
headerHeight={headerHeight}
|
||||||
isScrolledDown={isScrolledDown}
|
isScrolledDown={isScrolledDown}
|
||||||
onScroll={i === currentPage ? onScroll : noop}
|
onScroll={i === currentPage ? onScroll : noop}
|
||||||
|
forcedScrollY={i === currentPage ? null : clampedScrollY}
|
||||||
renderTab={
|
renderTab={
|
||||||
isHeaderReady && headerOnlyHeight > 0 && tabBarHeight > 0
|
isHeaderReady && headerOnlyHeight > 0 && tabBarHeight > 0
|
||||||
? child
|
? child
|
||||||
@@ -185,9 +182,41 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
|||||||
)
|
)
|
||||||
|
|
||||||
function PagerItem(
|
function PagerItem(
|
||||||
{headerHeight, isScrolledDown, onScroll, renderTab}: any /* TODO */,
|
{
|
||||||
|
headerHeight,
|
||||||
|
isScrolledDown,
|
||||||
|
onScroll,
|
||||||
|
renderTab,
|
||||||
|
forcedScrollY,
|
||||||
|
}: any /* TODO */,
|
||||||
) {
|
) {
|
||||||
const scrollElRef = useAnimatedRef()
|
const scrollElRef = useAnimatedRef()
|
||||||
|
useAnimatedReaction(
|
||||||
|
() => {
|
||||||
|
if (forcedScrollY) {
|
||||||
|
return forcedScrollY.value
|
||||||
|
} else {
|
||||||
|
// Active tab doesn't get synced.
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(nextForcedY, prevForcedY) => {
|
||||||
|
if (nextForcedY === prevForcedY) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (nextForcedY == null) {
|
||||||
|
// This tab just became active. It was already being synced before.
|
||||||
|
// So we don't need to do anything.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (prevForcedY == null) {
|
||||||
|
// This tab just became inactive, so it used to be the source of truth.
|
||||||
|
// There is no need to sync it until we get more scroll events.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scrollTo(scrollElRef, 0, nextForcedY, false)
|
||||||
|
},
|
||||||
|
)
|
||||||
if (renderTab == null) {
|
if (renderTab == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user