Reference version

This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.

Expo 路由器分栏视图

一个为原生分栏视图布局提供支持的 Expo Router 子模块。

iOS
Recommended version:
~57.0.0

expo-router/unstable-split-viewexpo-router 的一个子模块,导出用于使用 平台原生系统分栏视图 构建分栏视图布局的组件。

平台支持

Split View 仅可在 iOS 上使用。在其他平台上,SplitView 组件会自动回退为标准的 Slot 导航器进行渲染,确保你的应用无需条件代码即可跨所有平台正常工作。

iPhone 支持

在 iPhone 上,SplitView 会自动将所有列折叠为单一视图。一次只会显示一列。

选择初始列

使用 topColumnForCollapsing 属性来控制在分栏视图折叠时显示哪一列:

<SplitView topColumnForCollapsing="primary">{/* ... */}</SplitView>

可接受的值为 primarysupplementarysecondary。未设置时,系统将使用其默认行为。

在列之间导航

使用 ref 可以通过 show 方法以编程方式显示特定列:

import { useRef } from 'react'; import { Pressable, Text } from 'react-native'; import { SplitView } from 'expo-router/unstable-split-view'; import type { SplitHostCommands } from 'react-native-screens/experimental'; export default function Layout() { const ref = useRef<SplitHostCommands>(null); return ( <SplitView ref={ref} topColumnForCollapsing="primary"> <SplitView.Column> <Pressable onPress={() => ref.current?.show('secondary')}> <Text>显示主内容</Text> </Pressable> </SplitView.Column> </SplitView> ); }

已知限制

无法嵌套

导航层次结构中只能有一个 SplitView。尝试嵌套分屏视图将导致错误。

不能在其他导航器内部使用

SplitView 不能在另一个导航器内部使用(Slot 除外)。它必须用于根布局级别。

仅允许特定子项

SplitView 仅接受 SplitView.ColumnSplitView.Inspector 作为直接子项。其他组件将被忽略并给出警告。

标题暂时无法自定义

分屏视图列中的标题(导航栏)目前无法自定义。暂不支持自定义标题配置。

API 有限

当前 API 覆盖范围较小,可能无法满足所有使用场景。未来版本将添加更多属性和配置选项。

在 iPhone 上返回到上一列

要返回上一列,请点击导航栏中的系统返回按钮。未来版本将增加更细粒度的编程控制,以管理返回导航。

安装

要在你的项目中使用 expo-router/unstable-split-view,你需要在项目中安装 expo-router。请按照 Expo Router 安装指南中的说明进行操作:

安装 Expo Router

了解如何在你的项目中安装 Expo Router。

使用 SplitView.Column

SplitView.Column 用于在你的 split view 布局中定义额外的列。你可以在主内容区域之前最多添加两列。

双列布局

一个带主内容的简单侧边栏:

app/_layout.tsx
import { Link } from 'expo-router'; import { SplitView } from 'expo-router/unstable-split-view'; import { Text, Pressable } from 'react-native'; import { SafeAreaView } from 'react-native-screens/experimental'; export default function Layout() { return ( <SplitView> <SplitView.Column> <SafeAreaView edges={{ left: true, top: true }} style={{ flex: 1 }}> <Link href="/inbox"> <Pressable style={{ padding: 16 }}> <Text>收件箱</Text> </Pressable> </Link> <Link href="/sent"> <Pressable style={{ padding: 16 }}> <Text>已发送</Text> </Pressable> </Link> </SafeAreaView> </SplitView.Column> </SplitView> ); }

三列布局

一个带辅助列和主内容的侧边栏:

app/_layout.tsx
import { Link, useGlobalSearchParams } from 'expo-router'; import { SplitView } from 'expo-router/unstable-split-view'; import { SafeAreaView } from 'react-native-screens/experimental'; export default function Layout() { const params = useGlobalSearchParams(); return ( <SplitView> <SplitView.Column> <SafeAreaView edges={{ left: true, top: true }} style={{ flex: 1, gap: 16, padding: 16, }}> <Link href="/?col1=1" style={{ fontWeight: params.col1 === '1' ? 'bold' : 'normal' }}> 选项 1 </Link> <Link href="/?col1=2" style={{ fontWeight: params.col1 === '2' ? 'bold' : 'normal' }}> 选项 2 </Link> <Link href="/?col1=3" style={{ fontWeight: params.col1 === '3' ? 'bold' : 'normal' }}> 选项 3 </Link> </SafeAreaView> </SplitView.Column> <SplitView.Column> <SafeAreaView edges={{ left: true, top: true }} style={{ flex: 1, gap: 16, padding: 16, }}> <Link href={`/?col1=${params.col1}&col2=1`}>子选项 1</Link> <Link href={`/?col1=${params.col1}&col2=2`}>子选项 2</Link> <Link href={`/?col1=${params.col1}&col2=3`}>子选项 3</Link> </SafeAreaView> </SplitView.Column> </SplitView> ); }

使用 SplitView.Inspector

SplitView.Inspector 会添加一个从尾随边滑入的补充列,可用于显示更多细节或元数据:

<SplitView> <SplitView.Column>{/* 侧边栏 */}</SplitView.Column> <SplitView.Inspector> <View style={{ flex: 1, padding: 16 }}> <Text>检查器面板</Text> </View> </SplitView.Inspector> </SplitView>

完整示例

这是一个类似密码管理器的应用,包含三列:

app
_layout.tsx
index.tsx
[type]
  [id].tsx
  index.tsx
app/_layout.tsx
import { Link, Color, useGlobalSearchParams } from 'expo-router'; import { SplitView } from 'expo-router/unstable-split-view'; import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; import { SafeAreaView } from 'react-native-screens/experimental'; export default function Layout() { return ( <SplitView showInspector> <SplitView.Column> <PasscodeList /> </SplitView.Column> <SplitView.Column> <PasswordElementList /> </SplitView.Column> <SplitView.Inspector> <InspectorContent /> </SplitView.Inspector> </SplitView> ); } function PasscodeList() { return ( <SafeAreaView edges={{ top: true, left: true }} style={style.passcodeList}> <PasscodeCard title="全部" param="all" /> <PasscodeCard title="通行密钥" param="passkeys" /> <PasscodeCard title="代码" param="codes" /> <PasscodeCard title="安全" param="security" /> <PasscodeCard title="已删除" param="deleted" /> </SafeAreaView> ); } const passkeys = ['Github', 'Google', 'Facebook', 'Twitter', 'Apple', 'Microsoft', 'Amazon']; const security = ['Admin1234', 'Root']; const all = [...passkeys, ...security]; function PasswordElementList() { const params = useGlobalSearchParams(); const data = (() => { switch (params.type) { case 'all': case undefined: return all; case 'passkeys': return passkeys; case 'security': return security; default: return []; } })(); return ( <ScrollView contentInsetAdjustmentBehavior="automatic" style={{ backgroundColor: undefined }}> {data.map(item => ( <PasswordElement key={item} title={item} /> ))} </ScrollView> ); } function PasscodeCard({ param, title }: { param: string; title: string }) { const params = useGlobalSearchParams(); const isActive = params.type === param; return ( <Link href={`/${param}/`} disabled={isActive} style={[ style.passcodeCard, { backgroundColor: isActive ? Color.ios.systemBlue : Color.ios.systemGray6, }, ]} asChild> <Pressable> <Text style={{ color: isActive ? 'white' : 'black', fontSize: 16 }}>{title}</Text> </Pressable> </Link> ); } function PasswordElement({ title }: { title: string }) { const params = useGlobalSearchParams(); const isActive = params.id === title; return ( <Link href={`/${params.type}/${title}/`} asChild> <Pressable style={{ backgroundColor: isActive ? Color.ios.systemBlue : undefined, padding: 12, }}> <SafeAreaView edges={{ left: true }}> <Text style={{ color: isActive ? 'white' : 'black', fontSize: 16 }}>{title}</Text> </SafeAreaView> </Pressable> </Link> ); } function InspectorContent() { return ( <View style={style.inspectorContent}> <Text>检查器</Text> </View> ); } const style = StyleSheet.create({ passcodeList: { flex: 1, flexWrap: 'wrap', gap: 8, flexDirection: 'row', padding: 8, }, passcodeCard: { width: '48%', padding: 12, borderRadius: 12, justifyContent: 'center', alignItems: 'center', height: 50, }, inspectorContent: { flex: 1, justifyContent: 'center', alignItems: 'center', }, });
app/index.tsx
import { Redirect } from 'expo-router'; export default function Index() { return <Redirect href="/all/" />; }
app/[type]/index.tsx
import { Color } from 'expo-router'; import { Text, View } from 'react-native'; export default function Index() { return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Text style={{ color: Color.ios.label, fontSize: 24, fontWeight: 'bold' }}> 未选择任何内容 </Text> </View> ); }
app/[type]/[id].tsx
import { useLocalSearchParams } from 'expo-router'; import { Text, View } from 'react-native'; export default function Id() { const { id } = useLocalSearchParams(); return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Text>ID: {id}</Text> </View> ); }

API

import { SplitView } from 'expo-router/unstable-split-view';

Components

SplitView

Type: React.Element<SplitViewProps>

For full list of supported props, see SplitHostProps

SplitViewProps

children

Optional • Type: ReactNode

Inherited props

  • Omit<SplitHostProps, 'children'>

SplitView.Column

Type: React.Element<SplitViewColumnProps>

SplitViewColumnProps

children

Optional • Type: ReactNode

SplitView.Inspector

Only for:
iOS 26+

Type: React.Element<SplitViewColumnProps>