Expo 路由分屏视图

一个 Expo Router 子模块,提供原生分屏布局。

iOS
Bundled version:
~55.0.5

重要 SplitView 是一个 alpha 版 API,仅在 iOSExpo SDK 55 及更高版本可用。该 API 可能会发生重大更改,尚未准备好用于生产环境。

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

有关本地和 Web 应用的基于文件的路由库的更多信息,请参阅 Expo Router 参考资料。

平台支持

🌐 Platform support

分屏视图仅适用于 iOS。在其他平台上,SplitView 组件会自动回退为标准的 Slot 导航器,从而确保你的应用在所有平台上都能正常工作,无需条件代码。

🌐 Split View is only available on iOS. On other platforms, the SplitView component automatically falls back to rendering as a standard Slot navigator, ensuring your app works across all platforms without conditional code.

iPhone 支持

🌐 iPhone support

在 iPhone 上,SplitView 会自动将所有列折叠为一个视图。一次只能看到一列。

🌐 On iPhone, SplitView automatically collapses all columns into a single view. Only one column is visible at a time.

选择初始列

🌐 Choosing the initial column

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

🌐 Use the topColumnForCollapsing prop to control which column is displayed when the split view is collapsed:

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

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

🌐 Accepted values are primary, supplementary, and secondary. When not set, the system uses its default behavior.

在列之间导航

🌐 Navigating between columns

信息 .show() 方法需要 react-native-screens 4.24.0 或更高版本。SDK 55 包含 ~4.23.0,因此你需要手动安装 react-native-screens@~4.24.0 才能使用此功能。

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

🌐 Use a ref to programmatically show a specific column with the show method:

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>Show main content</Text> </Pressable> </SplitView.Column> </SplitView> ); }

已知的限制

🌐 Known limitations

不能嵌套

导航层级中只能有一个 SplitView。尝试嵌套拆分视图将会导致错误。

🌐 There can only be one SplitView in the navigation hierarchy. Attempting to nest split views will result in an error.

不能在其他导航器中使用

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

仅限特定儿童

SplitView 只接受 SplitView.ColumnSplitView.Inspector 作为直接子元素。其他组件将被忽略并发出警告。

标题尚无法自定义

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

🌐 The header (navigation bar) within split view columns cannot be customized. Custom header configurations are not yet supported.

有限的 API

当前的 API 表面最小,可能无法涵盖所有使用场景。未来的版本将添加额外的属性和配置选项。

🌐 The current API surface is minimal and may not cover all use cases. Additional props and configuration options will be added in future releases.

返回 iPhone 上的前一列

要返回到前一列,请点击导航栏中的系统返回按钮。未来的版本将增加对返回导航的更精细的程序控制。

🌐 To go back to a previous column, tap the system back button in the navigation bar. A future release will add more granular programmatic control over back navigation.

信息 注意: 我们正在积极开发 SplitView 并寻求反馈。你可以在 Discord 上分享你的想法,或者在 GitHub 上 提交问题,也可以使用本页底部的 反馈 按钮。

安装

🌐 Installation

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

🌐 To use expo-router/unstable-split-view in your project, you need to install expo-router in your project. Follow the instructions from Expo Router's installation guide:

安装 Expo 路由

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

使用 SplitView.Column

🌐 Using SplitView.Column

SplitView.Column 定义了拆分视图布局中的附加列。你可以在主内容区域之前添加最多两列。

两栏布局

🌐 Two-column layout

带有主要内容的简单侧边栏:

🌐 A simple sidebar with main content:

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>Inbox</Text> </Pressable> </Link> <Link href="/sent"> <Pressable style={{ padding: 16 }}> <Text>Sent</Text> </Pressable> </Link> </SafeAreaView> </SplitView.Column> </SplitView> ); }

三栏布局

🌐 Three-column layout

带有辅助栏和主要内容的侧边栏:

🌐 A sidebar with supporting column and main content:

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' }}> Option 1 </Link> <Link href="/?col1=2" style={{ fontWeight: params.col1 === '2' ? 'bold' : 'normal' }}> Option 2 </Link> <Link href="/?col1=3" style={{ fontWeight: params.col1 === '3' ? 'bold' : 'normal' }}> Option 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`}>Sub-Option 1</Link> <Link href={`/?col1=${params.col1}&col2=2`}>Sub-Option 2</Link> <Link href={`/?col1=${params.col1}&col2=3`}>Sub-Option 3</Link> </SafeAreaView> </SplitView.Column> </SplitView> ); }

使用 SplitView.Inspector

🌐 Using SplitView.Inspector

SplitView.Inspector 添加了一个从尾部滑入的补充列,可用于显示附加细节或元数据:

<SplitView> <SplitView.Column>{/* Sidebar */}</SplitView.Column> <SplitView.Inspector> <View style={{ flex: 1, padding: 16 }}> <Text>Inspector Panel</Text> </View> </SplitView.Inspector> </SplitView>

完整示例

🌐 Complete example

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

🌐 Here's a password manager-style app with three columns:

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>Inspector</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' }}> Nothing is selected </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

iOS

Type: React.Element<SplitViewProps>

For full list of supported props, see SplitHostProps

SplitViewProps

children

iOS
Optional • Type: ReactNode

Inherited Props

  • Omit<SplitHostProps, 'children'>

SplitView.Column

iOS

Type: React.Element<SplitViewColumnProps>

SplitViewColumnProps

children

iOS
Optional • Type: ReactNode

SplitView.Inspector

iOS 26+

Type: React.Element<SplitViewColumnProps>