[APP-1917] fix header measurement for android content disappearing on switch (#9964)

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
This commit is contained in:
Spence Pope
2026-03-02 14:20:16 -05:00
committed by GitHub
parent f614bf650d
commit a95b877ea3
+27 -9
View File
@@ -16,7 +16,6 @@ import Animated, {
useSharedValue,
} from 'react-native-reanimated'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {ScrollProvider} from '#/lib/ScrollContext'
import {
Pager,
@@ -72,19 +71,19 @@ export function PagerWithHeader({
const headerHeight = headerOnlyHeight + tabBarHeight
// capture the header bar sizing
const onTabBarLayout = useNonReactiveCallback((evt: LayoutChangeEvent) => {
const onTabBarLayout = useCallback((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) => {
}, [])
const onHeaderOnlyLayout = useCallback((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) => {
@@ -270,7 +269,8 @@ let PagerTabBar = ({
],
}
})
const headerRef = useRef(null)
const headerRef = useRef<View>(null)
const fallbackHeaderOnlyHeight = useRef(0)
return (
<Animated.View
pointerEvents={IS_IOS ? 'auto' : 'box-none'}
@@ -278,7 +278,19 @@ let PagerTabBar = ({
<View
ref={headerRef}
pointerEvents={IS_IOS ? 'auto' : 'box-none'}
collapsable={false}>
collapsable={false}
onLayout={(e: LayoutChangeEvent) => {
// Fallback measurement using onLayout directly on the header wrapper.
// This is more reliable than .measure() on Android after certain
// navigation transitions (e.g. returning from the logged-out view)
// where .measure() can fail to return a height. in general though,
// we should prefer using .measure() when possible as this can
// fire too early and cause layout thrashing.
// ref: https://github.com/bluesky-social/social-app/pull/9964 -sfp
if (isHeaderReady) {
fallbackHeaderOnlyHeight.current = e.nativeEvent.layout.height
}
}}>
{renderHeader?.({setMinimumHeight: setMinimumHeaderHeight})}
{
// It wouldn't be enough to place `onLayout` on the parent node because
@@ -286,14 +298,20 @@ let PagerTabBar = ({
// Instead, we'll render a brand node conditionally and get fresh layout.
isHeaderReady && (
<View
collapsable={false}
// It wouldn't be enough to do this in a `ref` of an effect because,
// even if `isHeaderReady` might have turned `true`, the associated
// layout might not have been performed yet on the native side.
onLayout={() => {
// @ts-ignore
headerRef.current?.measure(
(_x: number, _y: number, _width: number, height: number) => {
onHeaderOnlyLayout(height)
// sometimes height is `undefined` on Android, see above
if (height !== undefined) {
onHeaderOnlyLayout(height)
} else {
// if measure fails, use the value we got from `onLayout`
onHeaderOnlyLayout(fallbackHeaderOnlyHeight.current)
}
},
)
}}