Expo 路由分屏视图
一个 Expo Router 子模块,提供原生分屏布局。
For the complete documentation index, see llms.txt. Use this file to discover all available pages.
重要 SplitView 是一个仅在 iOS 上、Expo SDK 55 及更高版本中可用的 alpha API。该 API 可能会发生重大变化,目前尚不适合用于生产环境。
expo-router/unstable-split-view 是 expo-router 的一个子模块,导出用于使用 平台原生系统拆分视图 构建拆分视图布局的组件。
有关本地和网页应用文件路由库的更多信息,请参阅 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>
可接受的值为 primary、supplementary 和 secondary。未设置时,系统将使用默认行为。
🌐 Accepted values are primary, supplementary, and secondary. When not set, the system uses its default behavior.
在列之间导航
🌐 Navigating between columns
信息
.show()方法需要react-native-screens4.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.
标题尚无法自定义
分屏视图列中的标题(导航栏)无法自定义。目前尚不支持自定义标题配置。
🌐 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.
安装
🌐 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 Router。
使用 SplitView.Column
🌐 Using SplitView.Column
SplitView.Column 定义了拆分视图布局中的附加列。你可以在主内容区域之前添加最多两列。
两栏布局
🌐 Two-column layout
带有主要内容的简单侧边栏:
🌐 A simple sidebar with main content:
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:
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.tsxindex.tsx[type][id].tsxindex.tsximport { 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', }, });
import { Redirect } from 'expo-router'; export default function Index() { return <Redirect href="/all/" />; }
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> ); }
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
Type: React.Element<SplitViewProps>
For full list of supported props, see SplitHostProps
Type: React.Element<SplitViewColumnProps>
ReactNodeType: React.Element<SplitViewColumnProps>