# expo-glass-tabs
**适用于 Expo Router 的浮动液态玻璃标签栏 —— Revolut 风格的底部栏。**
[](https://www.npmjs.com/package/expo-glass-tabs)
[](https://www.npmjs.com/package/expo-glass-tabs)
[](#api)
[](./LICENSE)
## 为什么使用
iOS 26 的原生标签栏可以在滚动时最小化 —— 但它会折叠成一个**单一图标**。Revolut 的标签栏保持*所有*标签可见,只是去掉了文本标签,从而在两个维度上缩小了胶囊形状。这种行为无法通过原生的 `UITabBar` 实现,因此该包在 Expo Router 的 headless tabs 之上重建了它 —— 并使用了真正的原生材质。
## 功能
- 🪟 **真正的液态玻璃** —— 通过 `expo-glass-effect` 实现的 iOS 26 `UIGlassEffect`:真正的曲边圆角、边缘折射、内容透镜效果。在较旧的 iOS 和 Android 上提供稳定的后备方案。
- 📉 **滚动时最小化** —— 向下滚动时,胶囊在两个维度上缩小,同时文本标签折叠;每个图标保持可见。向上滚动以展开。采用临界阻尼弹簧,无闪烁(橡皮筋过度滚动已被过滤掉)。
- 🛝 **滑动高亮** —— 激活标签的胶囊通过一个可中断的、仅基于 transform 的弹簧在各个标签之间物理移动。
- 👆 **手指滑动** —— 沿标签栏拖动:高亮部分以 1:1 的比例追踪你的手指,图标在你滑过时亮起,在每个边界处触发触觉反馈,并在释放时进行导航 —— 屏幕绝不会在拖动过程中发生跳动。
- 🌫️ **渐进式边缘模糊** —— 内容在栏后逐渐溶解,没有生硬的模糊线。同一组件也适用于顶部栏。
- 🎞️ **细腻的屏幕过渡** —— 通过 `TabSlot` `renderFn` 在标签之间实现淡入淡出 + 微缩放。
- ⚡ **一切运行于 UI 线程** —— 所有动画都在带有原生手势识别器的 Reanimated worklet 中运行;阻塞的 JS 线程不会导致掉帧。
- 🧩 **纯 TypeScript** —— 没有自定义的原生代码。可在 Expo Go 和任何开发构建中运行。
## 安装
```
npx expo install expo-glass-tabs expo-blur expo-glass-effect expo-haptics expo-symbols react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-screens
```
将你的应用根节点包裹在 `GestureHandlerRootView` 中(Expo Router 不会为你执行此操作):
```
// app/_layout.tsx
import { GestureHandlerRootView } from 'react-native-gesture-handler';
export default function RootLayout() {
return
{/* ... */};
}
```
## 快速开始
```
import { useRouter } from 'expo-router';
import { Tabs, TabList, TabSlot, TabTrigger } from 'expo-router/ui';
import {
GlassTabBar,
GlassTabButton,
TabBarMinimizeProvider,
renderFadingTabScreen,
type GlassTabItem,
} from 'expo-glass-tabs';
const ITEMS: (GlassTabItem & { href: string })[] = [
{ name: 'index', href: '/', label: 'Home', icon: 'house.fill' },
{ name: 'invest', href: '/invest', label: 'Invest', icon: 'chart.line.uptrend.xyaxis' },
{ name: 'payments', href: '/payments', label: 'Payments', icon: 'arrow.left.arrow.right' },
{ name: 'crypto', href: '/crypto', label: 'Crypto', icon: 'bitcoinsign' },
];
export default function AppTabs() {
const router = useRouter();
return (
router.navigate(ITEMS[i].href as never)}>
{ITEMS.map(({ href, ...item }, index) => (
))}
);
}
```
然后在每个需要最小化标签栏的屏幕中附加滚动 hook:
```
import Animated from 'react-native-reanimated';
import { useMinimizeOnScroll } from 'expo-glass-tabs';
export default function HomeScreen() {
const onScroll = useMinimizeOnScroll();
return (
{/* content */}
);
}
```
## 自定义图标
当 SF Symbol 无法满足需求时(例如品牌 logo),请使用 `renderIcon`。它会在每个着色层被调用一次,因此激活/非激活的交叉淡入淡出仍保持在 UI 线程上:
```
import { Image } from 'expo-image';
const home: GlassTabItem = {
name: 'index',
label: 'Home',
renderIcon: ({ tint }) => (
),
};
```
## 主题设置
```
```
## 为你自己的边缘提供渐进式模糊
`ProgressiveBlur` 已作为独立组件导出 —— 将其放置在透明 header 之后即可:
```
import { ProgressiveBlur } from 'expo-glass-tabs';
;
```
## API
| 导出 | 类型 | 用途 |
| --- | --- | --- |
| `GlassTabBar` | component | 浮动胶囊 —— 通过 `TabList asChild` 使用 |
| `GlassTabButton` | component | 单个触发器 —— 通过 `TabTrigger asChild` 使用 |
| `TabBarMinimizeProvider` | component | 包裹 `Tabs` 树一次 |
| `useMinimizeOnScroll()` | hook | 用于 `Animated.ScrollView` 的滚动处理程序 |
| `useTabBarMinimized()` | hook | 原始的 0..1 最小化进度,用于自定义 UI |
| `renderFadingTabScreen` | function | 带有淡入淡出 + 微缩放效果的 `TabSlot` `renderFn` |
| `ProgressiveBlur` | component | 锚定在屏幕边缘的渐变模糊 |
| `MINIMIZE_SPRING` | constant | 弹簧配置,如果你想匹配它的话 |
## 工作原理
标签栏的*结构*在 JS 中声明(Expo Router headless tabs),但在运行时你看到和感受到的一切都是原生的:
- **材质** —— `UIGlassEffect`、`UIVisualEffectView`、SF Symbols、`UIFeedbackGenerator`。
- **运动** —— UI 线程上的 Reanimated worklet;滑动高亮和滑动操作仅基于 transform(GPU 合成,每帧无需布局计算)。
- **手势** —— 通过 `react-native-gesture-handler` 实现的原生识别器;`Pan`(滑动)与 `Tap` 竞争,因此点击保持宽容,同时拖动感觉就像是贴合在手指上一样。
圆角几何形状由玻璃视图本身(其原生的圆角配置)渲染,而不是 RN 的裁剪蒙版 —— 这就是能够在整个最小化动画中保持真正的曲边圆角和边缘光照效果的原因。
## 许可证
MIT © [David Mokos](https://github.com/davidmokos)