npx expo install expo-router react-native-safe-area-context react-native-screens expo-linking expo-constants expo-status-bar
{
"main": "expo-router/entry"
}
// app/index.js
import { StyleSheet, Text, View } from 'react-native';
export default function App() {
return (
<View style={styles.container}>
<Text style={styles.title}>
欢迎来到 Expo Router 课程!
</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 50,
width: 200,
fontWeight: 'bold',
color: '#e29447',
}
});
npx expo start
// 或者
npm run start
Invalid prop 'style' supplied to 'React.Fragment...'
{
"expo": {
"name": "dk-bms-app",
"slug": "dk-bms-app",
"scheme": "bms",
"version": "1.0.0",
// ...
}
}
这个配置叫做深度链接,这么配置之后,将来可以通过以下地址,从别的应用或者浏览器,直接跳转到该App对应的页面来。
bms://user
app
index.js
details.js
// app/index.js
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { Link } from 'expo-router';
export default function Index() {
return (
<View style={styles.container}>
<Text style={styles.title}>这里是首页</Text>
<Link href="/details" style={styles.link}>
跳转到详情页(Link)
</Link>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 40,
fontWeight: 'bold',
color: '#e29447',
},
link: {
marginTop: 20,
fontSize: 20,
color: '#1f99b0',
},
buttonText: {
marginTop: 20,
fontSize: 20,
color: '#ff7f6f',
},
});
// app/details.js
import { StyleSheet, Text, View } from 'react-native';
import { Link } from "expo-router";
export default function Details() {
return (
<View style={styles.container}>
<Text style={styles.title}>这里是详情页</Text>
<Link href="../" style={styles.backText}>
返回
</Link>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 40,
fontWeight: 'bold',
color: '#4f9df7',
},
backText: {
marginTop: 20,
fontSize: 20,
color: '#67c1b5',
}
});
export default function Index() {
return (
<View style={styles.container}>
// ...
<Link href="/details" asChild>
<TouchableOpacity>
<Text style={styles.buttonText}>
跳转到详情页(Link + asChild)
</Text>
</TouchableOpacity>
</Link>
</View>
);
}
在上述使用 Link 组件来跳转,点击链接之后,页面之间的切换没有任何动画效果,体验不太友好。另外,平常使用的那些App,页面都有个头部,里面有页面的标题之类的东西。
新建 _layout.js 文件
在 app 目录里,新建一个 _layout.js 文件,这是项目的布局文件。这个文件名称是固定的,前面必须有一个下划线 —。
布局的意思是,所有的页面都归它管理,而且是最先运行的文件,我们可以在里面做各种配置。
Stack 页面堆栈
从 expo-router 里,引用了一个叫做 Stack 的东西。Stack 是用于管理应用中的页面堆栈的。
在布局文件里加上Stack后,所有页面都会被Stack管理。进入新页面会从右侧 推入(Push),返回时 弹出(Pop) 页面,形成后进先出的这种结构。
// app/_layout.js
import { Stack } from 'expo-router';
export default function Layout() {
return <Stack />;
}
import { Stack } from 'expo-router';
export default function Layout() {
return (
<Stack
screenOptions={{
headerTitleAlign: 'center', // 安卓标题栏居中
animation: 'slide_from_right', // 安卓使用左右切屏
}}
/>
);
}
import { StyleSheet, Text } from 'react-native';
import { Slot } from 'expo-router';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
export default function Layout() {
return (
<SafeAreaProvider>
<SafeAreaView style={styles.container}>
<Text style={styles.header}>头部信息</Text>
<Slot/>
<Text style={styles.footer}>© Changle Weiyang Inc.</Text>
</SafeAreaView>
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
header: {
fontSize: 24,
textAlign: 'center'
},
footer: {
fontSize: 18,
textAlign: 'center'
}
});
除了使用 Link 组件进行页面跳转外,很多时候也会使用 JS 代码实现跳转。
在使用 router 之前,需要引入并声明定义:
// ...
import { useRouter } from 'expo-router';
export default function Index() {
const router = useRouter();
// ...
}
最常用的方法就是 router.navigate,在代码里加上一个按钮。
这种写法,与 Link组件的方式没有任何区别。它会将详情页加入Stack之中,进入详情页后,也是可以返回首页的。
export default function Index() {
const router = useRouter();
return (
<View style={styles.container}>
// ...
<TouchableOpacity onPress={() => router.navigate('/details')}>
<Text style={styles.buttonText}>
跳转(navigate )
</Text>
</TouchableOpacity>
</View>
);
}
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { useRouter } from 'expo-router';
export default function Details() {
const router = useRouter();
return (
<View style={styles.container}>
<Text style={styles.title}>这里是详情页</Text>
<TouchableOpacity onPress={() => router.navigate('/details')}>
<Text style={styles.buttonText}>
再次跳转(navigate)
</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 40,
fontWeight: 'bold',
color: '#4f9df7',
},
buttonText: {
marginTop: 20,
fontSize: 25,
color: '#ff7f6f',
},
backText: {
marginTop: 20,
fontSize: 25,
color: '#67c1b5',
}
});
replace 是替换的意思。用这种方式跳转,Stack里的所有页面都会被它替换掉。现在Stack里就只剩下详情页一个页面了,所以也就不能返回了。
那么现在我们只能按 r 键,刷新App,才能回到首页了。
export default function Index() {
const router = useRouter();
return (
<View style={styles.container}>
// ...
<TouchableOpacity onPress={() => router.replace('/details')}>
<Text style={styles.buttonText}>
替换(replace)
</Text>
</TouchableOpacity>
</View>
);
}
使用router.push,哪怕页面已经在Stack中了,也会继续强制将页面推入到Stack中。
export default function Details() {
const router = useRouter();
return (
<View style={styles.container}>
// ...
<TouchableOpacity onPress={() => router.push('/details')}>
<Text style={styles.buttonText}>
强制推入(push)
</Text>
</TouchableOpacity>
</View>
);
}
使用代码也可以实现返回操作,方法很简单,加上router.back,点击后,就可以一层一层的返回。
export default function Details() {
const router = useRouter();
return (
<View style={styles.container}>
// ...
<TouchableOpacity onPress={() => router.back()}>
<Text style={styles.backText}>
返回(back)
</Text>
</TouchableOpacity>
</View>
);
}
app
_layout.js
index.js
courses
[id].js
使用 useLocalSearchParams,可以从里面获取到传递过来的id。将id参数获取到后,显示到页面上。
import { View, Text, StyleSheet } from 'react-native';
import { useLocalSearchParams } from 'expo-router';
export default function Course() {
const { id } = useLocalSearchParams();
return (
<View style={styles.container}>
<Text style={styles.title}>这里是课程页</Text>
<Text style={styles.info}>课程ID: {id}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 40,
fontWeight: 'bold',
color: '#4f9df7',
},
info: {
marginTop: 20,
fontSize: 20,
color: '#67c1b5',
},
buttonText: {
marginTop: 20,
fontSize: 20,
color: '#ff7f6f',
},
});
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { Link, useRouter } from 'expo-router';
export default function Index() {
const router = useRouter();
return (
<View style={styles.container}>
<Text style={styles.title}>这里是首页</Text>
<Link style={styles.link} href="/courses/1">
跳转传参(Link)
</Link>
</View>
);
}
export default function Index() {
const router = useRouter();
return (
<View style={styles.container}>
// ...
<Link
style={styles.link}
href={{
pathname: '/courses/[id]',
params: { id: 2 }
}}>
跳转传参(Link 使用 params)
</Link>
</View>
);
}
export default function Index() {
const router = useRouter();
return (
<View style={styles.container}>
// ...
<TouchableOpacity onPress={() => router.navigate('/courses/3')}>
<Text style={styles.buttonText}>
跳转传参(navigate )
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => router.navigate({
pathname: '/courses/[id]',
params: { id: 4 }
})}>
<Text style={styles.buttonText}>
跳转传参(navigate 使用 params )
</Text>
</TouchableOpacity>
</View>
);
}
使用了Stack之后,就自动出现了顶部的这个导航栏。它里面的内容,也都是可以配置的。
import { Link, useRouter, Stack } from 'expo-router';
// app/index.js
export default function Index() {
const router = useRouter();
return (
<View style={styles.container}>
<Stack.Screen options={{ title: '首页' }}/>
// ...
</View>
);
}
可以在传参时将标题作为参数一起传递,然后在页面中获取传递的参数并设置。
export default function Index() {
const router = useRouter();
return (
<View style={styles.container}>
//...
<Link style={styles.link} href="/courses/1?title=Node.js">
跳转传参(Link)
</Link>
<Link
style={styles.link}
href={{
pathname: '/courses/[id]',
params: { id: 2, title: 'React Native' }
}}>
跳转传参(Link 使用 params)
</Link>
<TouchableOpacity onPress={() => router.navigate('/courses/3?title=Vue.js')}>
<Text style={styles.buttonText}>
跳转传参(navigate )
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => router.navigate({
pathname: '/courses/[id]',
params: { id: 4, title: '微信小程序' }
})}>
<Text style={styles.buttonText}>
跳转传参(navigate 使用 params )
</Text>
</TouchableOpacity>
</View>
);
}
export default function Course() {
const { id, title } = useLocalSearchParams();
return (
<View style={styles.container}>
<Stack.Screen options={{ title: title }}/>
// ...
</View>
);
}
有些时候,title设置了又想要修改它,可以通过navigation来实现。
export default function Course() {
const navigation = useNavigation();
// ...
return (
<View style={styles.container}>
// ...
<TouchableOpacity onPress={() => navigation.setOptions({ title: '课程太好了!' })}>
<Text style={styles.buttonText}>修改标题</Text>
</TouchableOpacity>
</View>
);
}
在options里,除了title以外,还加上了这些样式。
<Stack.Screen
options={{
title: '首页',
headerStyle: { // 导航栏整体样式
backgroundColor: '#e29447'
},
headerTintColor: '#fff', // 导航栏中文字、按钮、图标的颜色
headerTitleStyle: { // 导航栏标题样式
fontWeight: 'bold',
},
}}
/>
打开app/_layout.js,在screenOptions里加上以下配置,就可以看出布局文件里的screenOptions,是各个页面的公共配置。
import { Stack } from 'expo-router';
export default function Layout() {
return (
<Stack
screenOptions={{
headerTitleAlign: 'center', // 安卓标题栏居中
animation: 'slide_from_right', // 安卓使用左右切屏
headerStyle: { // 导航栏整体样式
backgroundColor: '#e29447'
},
headerTintColor: '#fff', // 导航栏中文字、按钮、图标的颜色
headerTitleStyle: { // 导航栏标题样式
fontWeight: 'bold',
},
}}
/>
);
}
先将原页面文件的Stack.Screen删掉,文件顶部的引用也删掉。
// 原页面文件的Stack.Screen
// <Stack.Screen options={{ title: '首页' }}/>
// 文件顶部的引用
// import { Stack } from 'expo-router';
然后在布局文件 _layout.js 的 Stack 中加上以下代码:
export default function Layout() {
return (
<Stack
// ...
>
<Stack.Screen name="index" options={{ title: '首页' }} />
</Stack>
);
}
这里要加一个name,name的值就和首页文件的路径对应。
因为首页文件和布局文件在同一目录下,所以直接写index就行,不用写其他路径。
最底下要改为 \<\/Stack>。
对于需要传递参数的,也可以按照如下方式进行修改:
import { Stack } from 'expo-router';
export default function Layout() {
return (
<Stack
// ...
>
// ...
<Stack.Screen
name="courses/[id]"
options={({ route }) => ({
title: route.params?.title || '详情页', // 使用 params 中的 title,如果没有则显示默认值
})}
/>
</Stack>
);
}
name 还是要对应课程页文件所在的路径。这里因为和布局文件不在同一层,所以要补全路径 courses/。
接着设置标题,要先写个options,也就是详情页的设置了。里面写个route,这样就可以使用router.params?.title获取到路由参数里的标题了。如果没有传标题过来,就默认设置成详情页。
import { Stack } from 'expo-router';
import { Image } from 'expo-image';
import { StyleSheet } from 'react-native';
export default function Layout() {
return (
<Stack
screenOptions={{
headerTitleAlign: 'center', // 安卓标题栏居中
animation: 'slide_from_right', // 安卓使用左右切屏
headerTintColor: '#1f99b0', // 导航栏中文字、按钮、图标的颜色
// 标题组件的样式
headerTitleStyle: {
fontWeight: '400',
color: '#2A2929',
fontSize: 16,
},
}}
>
<Stack.Screen name='index' options={{
headerTitle: props => <LogoTitle {...props} />
}} />
</Stack>
);
}
/**
* 导航栏Logo
*/
function LogoTitle() {
return <Image style={style.logo} containFit="contain" source={require('../assets/favicon.png')} />
}
const style = StyleSheet.create({
logo: {
width: 30,
height: 30,
},
});
定义导航栏左侧按钮就是在 Stack.screen 的 options 增加一个 headerLeft 配置。
/**
* 导航栏左侧组件
*/
function HeaderLeft() {
return (
<Link asChild href="/articles" style={style.headerLeft}>
<TouchableOpacity>
<SimpleLineIcons size={20} color="#1f99b0" name="bell"/>
</TouchableOpacity>
</Link>
);
}
定义导航栏右侧组件就是在 Stack.screen 的 options 增加一个 headerRight 配置。
如果想配置多个,就在组件中定义多个。
/**
* 导航栏右侧组件
*/
function HeaderRight() {
return (
<>
<Link href="/search" style={style.headerRight} asChild>
<TouchableOpacity>
<SimpleLineIcons size={20} color="#1f99b0" name="magnifier"/>
</TouchableOpacity>
</Link>
<Link href="/settings" style={style.headerRight} asChild>
<TouchableOpacity>
<SimpleLineIcons size={20} color="#1f99b0" name="options"/>
</TouchableOpacity>
</Link>
</>
)
}
/**
* 导航栏按钮组件
* @param props
*/
function HeaderButton(props) {
const { name, ...rest } = props;
return (
<Link asChild {...rest} >
<TouchableOpacity>
<SimpleLineIcons size={20} color="#1f99b0" name={name} />
</TouchableOpacity>
</Link>
);
}
<Stack.Screen
name="index"
options={{
headerTitle: props => <LogoTitle {...props} />,
headerLeft: () => <HeaderButton name="bell" href="/articles" style={style.headerLeft} />,
headerRight: () => (
<>
<HeaderButton name="magnifier" href="/search" style={style.headerRight} />
<HeaderButton name="options" href="/settings" style={style.headerRight} />
</>
),
}}
/>
因为没有给当前页面设置title,所以中间的标题,显示的是路径:articles/index。iOS左侧的返回按钮,在箭头后面跟了个index。而在Android上,返回按钮后面又没有这个index。
这两个地方的默认显示,都让人感觉比较奇怪。我们添加点配置,打开app/_layout.js,找到最外层的screenOptions:
export default function Layout() {
return (
<Stack
screenOptions={{
title: '', // 默认标题为空
//...
headerBackButtonDisplayMode: 'minimal', // 设置返回按钮只显示箭头,不显示文字
}}
>
//...
</Stack>
);
}
将title设置成空,这样如果当前页面没有设置标题,就什么都不要显示。下面,增加了一个headerBackButtonDisplayMode属性。这样设置后,导航栏的返回按钮就只显示箭头,不会自己带上提示文字了。
这个 (tabs) 目录,就是专门用来放各个Tab页的。
现在这样做,就是有三个Tab页,一个首页,一个视频课程页,一个用户页。
(tabs)目录,名字上的这个小括号,在Expo Router里是有特殊意义的,它叫做路由分组:
- 利用它,将一些相关的文件,组织在一起。
- 特别要注意,这种带小括号的目录名,在URL里不计算路径,你要当它不存在!
index.js的URL,依然还是/index,就像还在app目录里一样,它依然还是首页。
videos.js文件的URL,其实是/videos,而不是/(tabs)/videos。
同样的道理,users.js文件的URL,是/users。
打开app/_layout.js。Stack 增加了 TabBar 的配置。
- 注意这里有个headerShown,配置成了false。这是因为TabBar也会自带一个导航栏。如果不隐藏,它会和Stack的导航栏同时出现,这就会出来两个导航栏了。
- 底下给各个页面都添加上了title。
- 注意注释这里有个Cards,在Stack里页面是有两种形式的:
跳转时,这种页面左右滑动切换的就叫Cards。
另一种页面从屏幕底部弹出的,叫做模态(Modal)。
import { Stack } from 'expo-router';
export default function Layout() {
return (
<Stack
screenOptions={{
title: '', // 默认标题为空
headerTitleAlign: 'center', // 安卓标题栏居中
animation: 'slide_from_right', // 安卓使用左右切屏
headerTintColor: '#1f99b0', // 导航栏中文字、按钮、图标的颜色
// 标题组件的样式
headerTitleStyle: {
fontWeight: '400',
color: '#2A2929',
fontSize: 16,
},
headerBackButtonDisplayMode: 'minimal', // 设置返回按钮只显示箭头,不显示文字
}}
>
{/* Tabs */}
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
{/* Cards */}
<Stack.Screen name="articles/index" options={{ title: '通知' }} />
<Stack.Screen name="settings/index" options={{ title: '设置' }} />
<Stack.Screen name="courses/[id]" options={{ title: '课程详情' }} />
<Stack.Screen name="search/index" options={{ title: '搜索' }} />
</Stack>
);
}
import { Tabs, Link } from 'expo-router';
import { Image } from 'expo-image';
import { SimpleLineIcons } from '@expo/vector-icons';
import { StyleSheet, TouchableOpacity } from 'react-native';
/**
* 导航栏 Logo 组件
*/
function LogoTitle() {
return <Image style={style.logo} contentFit="contain" source={require('../../assets/logo-light.png')}/>;
}
/**
* 导航栏按钮组件
* @param props
*/
function HeaderButton(props) {
const { name, ...rest } = props;
return (
<Link asChild {...rest} >
<TouchableOpacity>
<SimpleLineIcons size={20} color="#1f99b0" name={name} />
</TouchableOpacity>
</Link>
);
}
export default function TabLayout() {
return (
<Tabs
screenOptions={{
headerTitleAlign: 'center', // 安卓标题栏居中
headerTitle: props => <LogoTitle {...props} />,
headerLeft: () => <HeaderButton name="bell" href="/articles" style={style.headerLeft} />,
headerRight: () => (
<>
<HeaderButton name="magnifier" href="/search" style={style.headerRight} />
<HeaderButton name="options" href="/settings" style={style.headerRight} />
</>
),
}}
>
<Tabs.Screen
name="index"
options={{ title: '发现' }}
/>
<Tabs.Screen
name="videos"
options={{ title: '视频课程' }}
/>
<Tabs.Screen
name="users"
options={{ title: '我的' }}
/>
</Tabs>
);
}
const style = StyleSheet.create({
logo: {
width: 130,
height: 30,
},
headerLeft: {
marginLeft: 15,
},
headerRight: {
marginRight: 15,
}
});
默认TabBar上没有图标,显示了一个倒三角形。需要加个配置,打开app/(tabs)/_layout.js,增加一个TabBar图标组件:
/**
* TabBar 图标组件
* @param props
*/
function TabBarIcon(props) {
return <SimpleLineIcons size={25} {...props} />;
}
然后要将这个图标组件,配置到每一个Tabs.Screen里:
export default function TabLayout() {
return (
<Tabs
//...
>
<Tabs.Screen
name="index"
options={{
title: '发现',
tabBarIcon: ({ color }) => <TabBarIcon name="compass" color={color} />,
}}
/>
<Tabs.Screen
name="videos"
options={{
title: '视频课程',
tabBarIcon: ({ color }) => <TabBarIcon name="camrecorder" color={color} />,
}}
/>
<Tabs.Screen
name="users"
options={{
title: '我的',
tabBarIcon: ({ color }) => <TabBarIcon name="user" color={color} />,
}}
/>
</Tabs>
);
}
默认是显示了个蓝色的图标,不是很好看。继续在screenOptions里加上颜色配置:
export default function TabLayout() {
return (
<Tabs
screenOptions={{
//...
tabBarActiveTintColor: '#1f99b0', // 设置 TabBar 选中项的颜色
}}
>
//...
</Tabs>
);
}
在Android上,点击Tabbar后,发现出现了奇怪的水波纹特效,这个效果是Android系统自己搞出来的,无论在模拟器还是真机上都存在。
对于这种效果,我们也可以通过配置去掉它。继续加上这一段tabBarButton的配置:
export default function TabLayout() {
return (
<Tabs
screenOptions={{
//...
// Android 取消水波纹效果
tabBarButton: (props) => (
<TouchableOpacity
{...props}
activeOpacity={1}
style={[props.style, { backgroundColor: 'transparent' }]}
/>
),
}}
>
//...
</Tabs>
);
}
在App里,经常还会看到一种从下向上弹出的页面,这个就叫做模态页。
在 app 目录下新增一个目录和文件,例如teachers/[id].js,里面就放一些基础信息:
import { View, Text, StyleSheet } from 'react-native';
export default function Teacher() {
return (
<View style={styles.container}>
<Text style={styles.title}>这里是老师详情页!</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 40,
fontWeight: 'bold',
color: '#e29447',
},
});
export default function Layout() {
return (
<Stack
//...
>
//...
{/* Modal */}
<Stack.Screen
name="teachers/[id]"
options={{
presentation: 'modal',
title: '老师详情',
}}
/>
//...
</Stack>
);
}
export default function Index() {
return (
<View style={styles.container}>
//...
<Link style={styles.link} href="/teachers/1">
打开教师页(Modal)
</Link>
</View>
);
}
自定义一个关闭按钮好了。打开app/_layout.js文件,增加一个关闭按钮组件:
import { Stack, useRouter } from 'expo-router';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { TouchableOpacity, View } from 'react-native';
/**
* 模态页关闭按钮组件
*/
function CloseButton() {
const router = useRouter();
return (
<View style={{ width: 30 }}>
<TouchableOpacity onPress={() => router.dismiss()}>
<MaterialCommunityIcons name="close" size={30} color="#1f99b0" />
</TouchableOpacity>
</View>
);
}
然后将这个自定义关闭按钮,用headerLeft配置到模态页的左侧:
export default function Layout() {
return (
<Stack
//...
>
//...
{/* Modal */}
<Stack.Screen
name="teachers/[id]"
options={{
presentation: 'modal',
title: '老师详情',
headerLeft: () => <CloseButton />,
}}
/>
//...
</Stack>
);
}
还有一种模态页,也是从下向上弹出,但是是全屏的。可以将模态配置这里改为fullScreenModal:
<Stack.Screen
name="modal"
options={{
presentation: 'fullScreenModal', // 全屏模态
// ...
}}
/>
用Andorid的同学,这样配置了以后,会发现并没有这种从下向上弹出的效果,依然还是左右切换的。
这是由于Andorid系统自身机制不同导致的,不过我们也可以加点配置,实现个类似的效果,配置里加上animation:
<Stack.Screen
name="modal"
options={{
presentation: 'modal',
animation: 'slide_from_bottom', // 从底部滑入
// ...
}}
/>
在Android上弹出的模态页面只能全屏显示,无法实现和iOS一样的堆叠效果。