Expo 路由分栏视图
一个 Expo Router 子模块,提供原生分栏视图布局。
For the complete documentation index, see llms.txt. Use this Use this file to discover all available pages.
重要 SplitView 是一个 alpha API,仅在 Expo SDK 55 及更高版本中可用于 iOS。该 API 可能会发生破坏性更改,目前尚未准备好用于生产环境。
expo-router/unstable-split-view 是 expo-router 的一个子模块,导出用于使用 平台原生系统分栏视图 构建分屏布局的组件。
有关适用于原生和 Web 应用的基于文件路由库的更多信息,请参阅 Expo Router 参考文档。
平台支持
Split View 仅适用于 iOS。在其他平台上,SplitView 组件会自动回退为标准的 Slot 导航器,从而确保你的应用无需条件代码即可在所有平台上正常工作。
iPhone 支持
在 iPhone 上,SplitView 会自动将所有列折叠为单一视图。任一时刻只会显示一列。
选择初始列
使用 topColumnForCollapsing 属性来控制在分屏折叠时显示哪一列:
<SplitView topColumnForCollapsing="primary">{/* ... */}</SplitView>
可接受的值为 primary、supplementary 和 secondary。如果未设置,系统将使用其默认行为。
在列之间导航
.show()方法需要react-native-screens4.24.0 或更高版本。SDK 55 捆绑的是~4.23.0,因此你需要手动安装react-native-screens@~4.24.0才能使用此功能。
使用 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> ); }
已知限制
安装
要在你的项目中使用 expo-router/unstable-split-view,你需要先在项目中安装 expo-router。请按照 Expo Router 安装指南中的说明进行操作:
了解如何在你的项目中安装 Expo Router。
使用 SplitView.Column
SplitView.Column 用于定义分屏布局中的附加列。你最多可以在主内容区域之前添加两列。
双列布局
一个简单的侧边栏加主内容布局:
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> ); }
三列布局
一个带辅助列和主内容的侧边栏布局:
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.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>检查器</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' }}> 未选择任何内容 </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>