Expo 路由分屏视图
一个 Expo Router 子模块,提供原生分屏布局。
重要 SplitView 是一个 alpha 版 API,仅在 iOS 且 Expo SDK 55 及更高版本可用。该 API 可能会发生重大更改,尚未准备好用于生产环境。
expo-router/unstable-split-view 是 expo-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>
可接受的值为 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>