Expo 54 在 iOS 26 下的 Liquid Glass 导航栏按钮为何间歇性变宽?
结论
仅凭这两段代码,无法确定按钮变宽的根因,也不能确认这是 Expo 54 或 iOS 26 的已知缺陷。代码已经为 MenuView 设置了 36 × 36 的尺寸约束。下一步应测量异常发生时各层的实际尺寸,先区分是 React Native 内容变宽,还是导航栏玻璃背景变宽,再分别排查。
目前还缺少导航栏注册代码、./Pressable 的实现,以及导航库和相关原生依赖的具体版本,这些都会影响判断。
现有代码中有哪些关键点?
当 shouldUseConstrainedAnchor 为 true 时,当前布局关系是:
MenuView:36 × 36
└── View:100% × 100%
└── HeaderButton:100% × 100%
└── InnerContent:横向排列 children
有几个地方需要确认:
- 尺寸约束只在
Platform.OS === 'ios' && isLiquidGlassAvailable()成立时生效。应记录这个条件的实际值,不能仅凭系统版本认定约束已经启用。 HeaderButton的样式数组把传入的style放在最后。在自定义Pressable按常规方式应用该数组的前提下,styles.visualButton.paddingHorizontal: 0会覆盖前面的paddingHorizontal: 6。目前没有足够证据直接把变宽归因于这 6 个单位的内边距。Pressable来自./Pressable,现有材料无法判断其内部是否增加了容器、动画或尺寸样式。InnerContent没有设置宽度。需要检查实际传入的children是否有动态文字、条件渲染或自身尺寸变化。
AccessibleMenuView 的类型断言只影响 TypeScript 检查,不能据此判断运行时是否正确处理了新增的 accessibility 属性。
第一步:记录布局,确认究竟是哪一层变宽
可以先在 HeaderMenuButton.tsx 中增加一个诊断函数:
import { type LayoutChangeEvent } from 'react-native';
const traceLayout = (name: string) => (event: LayoutChangeEvent) => {
const { x, y, width, height } = event.nativeEvent.layout;
console.log(`[header] ${name}`, { x, y, width, height });
};
在现有的内容 View 上增加 onLayout:
<View
onLayout={traceLayout('menu-content')}
pointerEvents="none"
accessible={false}
accessibilityElementsHidden
importantForAccessibility="no-hide-descendants"
style={shouldUseConstrainedAnchor ? styles.content : undefined}
>
<HeaderButton
accessibilityLabel={accessibilityLabel}
style={shouldUseConstrainedAnchor ? styles.visualButton : undefined}
>
{children}
</HeaderButton>
</View>
同时记录约束条件:
console.log('[header] constrained anchor', shouldUseConstrainedAnchor);
在创建导航栏右侧内容的位置,也给已有的 React Native 外层容器添加 onLayout。如果没有外层容器,可以临时添加一个 View 测量。但新增容器本身可能影响布局,因此只能作为诊断对照。
观察异常发生前后的日志和画面:
| 观察结果 | 下一步检查 |
|---|---|
内容 View 的宽度发生变化 | 约束条件、父容器布局、自定义 Pressable 和 children |
| 内容保持 36 × 36,但外层容器变宽 | 导航栏右侧内容的包装方式及其样式 |
| React Native 各层尺寸稳定,只有玻璃背景变宽 | 原生导航栏对自定义视图的承载和背景呈现 |
onLayout 测得的是对应 React Native 节点的布局,不能直接代表原生玻璃背景的边界。需要结合尺寸日志和异常时的截图或录屏判断。
第二步:逐层替换,缩小复现范围
在同一个导航栏位置依次进行以下对照,每次只改变一项:
- 使用 React Native 自带的
Pressable和固定尺寸的静态内容,暂时去掉MenuView、自定义Pressable与InnerContent。 - 在基础按钮外恢复
MenuView,保持固定尺寸。 - 恢复项目的
HeaderButton和实际children。
第一组可以使用下面的组件:
import React from 'react';
import { Pressable as RNPressable, Text } from 'react-native';
export function HeaderSizeProbe() {
return (
<RNPressable
accessibilityRole="button"
accessibilityLabel="测试按钮"
onPress={() => console.log('[header] pressed')}
onLayout={({ nativeEvent: { layout } }) => {
console.log('[header] probe', layout);
}}
style={{
width: 36,
height: 36,
flexGrow: 0,
flexShrink: 0,
alignItems: 'center',
justifyContent: 'center',
}}
>
<Text>⋯</Text>
</RNPressable>
);
}
这里沿用了原代码中的 36,方便比较;它不是对 iOS 导航栏按钮标准尺寸的断言。
如果基础按钮也变宽,优先检查导航栏配置和原生容器。如果恢复 MenuView 后才复现,重点检查菜单视图与导航栏的交互。如果恢复自定义 HeaderButton 后才复现,就继续检查 ./Pressable 和传入内容。
故障是间歇性的,某一次没有复现不能证明某层没有问题。各组应尽量重复相同的页面进入、返回和菜单操作,并记录实际触发步骤。
第三步:根据定位结果调整
如果日志确认 React Native 外层容器被拉伸,可以把明确的尺寸约束放到实际承载右侧按钮的根节点,并检查是否存在 flex: 1、flexGrow 或过大的 minWidth。当前代码已经约束了 MenuView,重复添加同样的宽高未必能解决问题。
如果问题来自自定义 Pressable,应修正它内部的样式合并或包装布局。需要检查最终承载样式的节点,不能只看调用处传入的 style。
如果各层布局尺寸都稳定,只有玻璃背景发生变化,需要补充导航栏配置和最小复现,再判断是否与原生导航集成有关。在明确导航库及版本之前,不宜给出关闭玻璃效果或背景的具体配置名称。
还需要哪些信息?
进一步定位时,最有用的是以下材料:
- 注册右侧按钮的完整导航配置,以及所用导航库名称。
./Pressable的实现和实际传入的children。- Expo、React Native、导航相关依赖及
@react-native-menu/menu的具体版本。 - 正常与异常时的截图、尺寸日志,以及是否在打开菜单或页面返回后触发。
现有材料不足以承诺某个补丁版本能够修复。若要通过调整依赖验证,应在项目兼容范围内进行,并用相同复现步骤比较调整前后的表现。
备注:内容仅供参考。
