PAYATHON 2026

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 节点的布局,不能直接代表原生玻璃背景的边界。需要结合尺寸日志和异常时的截图或录屏判断。

第二步:逐层替换,缩小复现范围

在同一个导航栏位置依次进行以下对照,每次只改变一项:

  1. 使用 React Native 自带的 Pressable 和固定尺寸的静态内容,暂时去掉 MenuView、自定义 Pressable 与 InnerContent。
  2. 在基础按钮外恢复 MenuView,保持固定尺寸。
  3. 恢复项目的 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 的具体版本。
  • 正常与异常时的截图、尺寸日志,以及是否在打开菜单或页面返回后触发。

现有材料不足以承诺某个补丁版本能够修复。若要通过调整依赖验证,应在项目兼容范围内进行,并用相同复现步骤比较调整前后的表现。

备注:内容仅供参考。