callstack / callstack/react-native-bottom-tabs
[Android] Content doesn't resize when tab bar visibility changes (tabBarHidden)
- 主要语言
- TypeScript
- 星标
- 1.5k
- 派生
- 109
- 平均合并
- 11 小时 44 分钟
- 30 天内合并 PR
- 8
描述
### Before submitting a new issue
- [x] I tested using the latest version of the library, as the bug might be already fixed.
- [x] I tested using a supported version of react native.
- [x] I checked for possible duplicate issues, with possible answers.
### Bug summary
On Android, when `tabBarHidden` is toggled to `true`, the native bottom navigation view is set to `GONE` and its space is reclaimed at the native level, but the React content does not grow to fill it. The screen keeps its old height, leaving a blank strip at the bottom exactly the height of the tab bar. Bottom-anchored elements (`position: 'absolute', bottom: 0`) end up floating ~80dp above the actual bottom of the screen. Toggling back to visible has the same problem in reverse until something else triggers a relayout.
I tracked it down to the layout listener in `RCTTabView.kt`. The size-change guard compares the bounds of the whole container (`ReactBottomNavigationView`), but when only the tab bar visibility changes, the container keeps the exact same size — only the inner `layoutHolder` grows or shrinks:
```kotlin
post {
addOnLayoutChangeListener { _, left, top, right, bottom, _, _, _, _ ->
val newWidth = right - left // container size — unchanged when tab bar is hidden
val newHeight = bottom - top
onTabBarMeasuredListener?.invoke(...)
if (newWidth != lastReportedSize?.width || newHeight != lastReportedSize?.height) {
// never reached in this case, so onNativeLayoutListener doesn't fire
onNativeLayoutListener?.invoke(dpWidth, dpHeight)
lastReportedSize = Size(newWidth, newHeight)
}
}
}
```
Two problems compound here:
1. The guard compares the container size, while the value actually reported to JS is the `layoutHolder` size. When the tab bar is hidden, the container stays the same but `layoutHolder` changes, so the guard filters the update out.
2. The listener is attached to the container, and `OnLayoutChangeListener` only fires when that view's own bounds change — which they don't in this scenario. So even the callback itself may not run.
As a result `onNativeLayout` never reaches the JS side, `setMeasuredDimensions` keeps the stale height, and the children rendered by `TabView` stay at the old size.
Verified on an emulator with `uiautomator dump`: after hiding the tab bar, the native `layoutHolder` grows to full screen height (2400px on my device), while the React subtree inside it stays at the previous 2126px.
iOS is not affected.
### Library version
1.4.0
### Environment info
```shell
System:
OS: macOS 26.5.2
CPU: (18) arm64 Apple M5 Pro
Binaries:
Node: 24.13.0
Yarn: 3.6.4
npm: 11.6.2
npmPackages:
react: 19.2.3
react-native: 0.86.0
Android:
hermesEnabled: true
newArchEnabled: true
iOS:
hermesEnabled: true
newArchEnabled: true
```
### Steps to reproduce
1. Android app using `createNativeBottomTabNavigator` (New Architecture).
2. Render a screen with a bottom-anchored view, e.g. an action bar with `position: 'absolute', bottom: 0`.
3. Toggle `tabBarHidden` to `true` (typical case: entering a selection/edit mode).
4. The tab bar disappears, but the bottom-anchored view stays where the tab bar's top edge used to be, with an empty strip below it.
Tested on an Android 16 emulator (arm64), RN 0.86, and reproduced consistently.
### Reproducible sample code
```tsx
const [hidden, setHidden] = useState(false);
{/* screen content */}
setHidden(v => !v)} />
```
I have a fix working locally (report the `layoutHolder` size instead, and also listen to `layoutHolder` layout changes so the visibility-toggle case is caught). Will open a PR.
贡献指南
调研方向
首先阅读 android/src/main/java/com/th3rdwave/safeareacontext/RCTTabView.kt,并通过切换 tabBarHidden 复现提供的 Android 示例。当标签栏隐藏时,React 内容和底部锚定的视图调整为完整的可用高度,并在标签栏显示时正确恢复,即表示完成。
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- android, kotlin, react-native
- 领域
- mobile
- Issue 类型
- 缺陷
- 难度
- 3/5
- 预计耗时
- 1-2 天
- 活跃度
- 冷清
- 描述清晰度
- 描述清楚
- 新手友好度
- 35/100