个人App应用程序上须要一个抽屉菜单。React navigation drawer导航支持此功能,可是改变了屏幕的结构,我不但愿更改,由于我只是其中一个屏幕上须要用到这个简单抽屉菜单组件。大体的效果以下:javascript
react-native-modal
组件大体能够知足个人需求,模态框加上左右移入移出的动画加上手势基本能实现侧拉抽屉的组件。如今安装它:java
yarn add react-native-modal -save
SideMenu组件是侧拉菜单里面展现的内容react
import React from 'react'; import { Text, View, SafeAreaView } from 'react-native'; import styles from './styles'; const Title = ({ title }) => { return <Text style={styles.title}>{title}</Text>; }; const SwitchText = ({ text }) => { return <Text style={styles.switchText}>{text}</Text>; }; const Description = ({ text }) => { return <Text style={styles.description}>{text}</Text>; }; const SideMenu = props => { return ( <SafeAreaView style={styles.safeAreaView}> <View style={styles.container}> <Title title="Timeline" /> <View> <View style={styles.swithBlock}> <SwitchText text="Ratings with reviews only" /> </View> <Description text="When enabled, on your timeline we will only show ratings with reviews." /> </View> </View> <View style={styles.footer}> <Text style={styles.link}>Press to call parent function</Text> </View> </SafeAreaView> ); }; export default SideMenu;
import { StyleSheet } from 'react-native'; import { screenSize } from '../../../utils/tools'; const styles = StyleSheet.create({ safeAreaView: { flex: 1, backgroundColor: '#fff' }, container: { margin: 12, flex: 1 }, title: { marginTop: 15, marginBottom: 10, color: '#444', fontSize: 14 }, swithBlock: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, switchText: { fontSize: 14, color: '#222' }, link: { padding: 5, color: '#892853' }, description: { fontSize: 13, color: '#555', marginTop: 12, marginBottom: 6 } }); export default styles;
引用组件,经过isVisible
参数控制菜单显示隐藏,toggleSideMenu
方法控制切换显示隐藏,还有一些控制入场动画的参数。我为了使它更接近抽屉组件,因此使用slideInLeft
。react-native
import React, { useState } from 'react'; import { Text, TouchableOpacity, View } from 'react-native'; import styles from './styles'; import { ButtonGroup, Header } from 'react-native-elements'; import common from '../../styles/common'; import Modal from 'react-native-modal'; import SideMenu from './SideMenu'; import Ionicons from 'react-native-vector-icons/Ionicons'; import { useNavigation } from '@react-navigation/native'; const ProjectDetail = props => { const { route } = props; console.log('路由参数', route.params); const [visible, setVisible] = useState(false); const [selectedIndex, setSelectedIndex] = useState(0); const navigation = useNavigation(); const toggleSideMenu = () => { setVisible(!visible); }; const updateIndex = index => { setSelectedIndex(index); }; const component1 = () => <Text>文件</Text>; const component2 = () => <Text>流程中心</Text>; const buttons = [{ element: component1 }, { element: component2 }]; return ( <View style={common.container}> <Header leftComponent={ <View> <TouchableOpacity onPress={navigation.goBack}> <Ionicons name="arrow-back" size={24} color={'#fff'} /> </TouchableOpacity> </View> } centerComponent={{ text: 'MY TITLE', style: { color: '#fff' } }} rightComponent={ <View> <TouchableOpacity onPress={toggleSideMenu}> <Ionicons name="menu" size={24} color={'#fff'} /> </TouchableOpacity> </View> } /> <ButtonGroup onPress={updateIndex} selectedIndex={selectedIndex} buttons={buttons} containerStyle={{ height: 28 }} /> <Modal isVisible={visible} onBackdropPress={toggleSideMenu} // Android back press onSwipeComplete={toggleSideMenu} // Swipe to discard animationIn="slideInLeft" // Has others, we want slide in from the left animationOut="slideOutLeft" // When discarding the drawer swipeDirection="left" // Discard the drawer with swipe to left useNativeDriver // Faster animation hideModalContentWhileAnimating // Better performance, try with/without propagateSwipe // Allows swipe events to propagate to children components (eg a ScrollView inside a modal) style={styles.sideMenuStyle} > <SideMenu /> </Modal> </View> ); }; export default ProjectDetail;
import { StyleSheet } from 'react-native'; import { screenSize } from '../../utils/tools'; const styles = StyleSheet.create({ sideMenuStyle: { width: screenSize.width * 0.75, margin: 0 } }); export default styles;
由于其余时候有可能部分页面也须要相似的抽屉组件,因此把这个Modal
组件封装一下,首先想到咱们须要在外部(有多是在页面头部菜单)点击出发显示菜单的功能,因此必须暴露给父组件调用子组件内部方法,那么咱们就须要forwardRef
和useImperativeHandle
:forwardRef
:引用父组件的ref实例,成为子组件的一个参数,能够引用父组件的ref绑定到子组件自身的节点上.useImperativeHandle
: 第一个参数,接收一个经过forwardRef引用父组件的ref实例,第二个参数一个回调函数,返回一个对象,对象里面存储须要暴露给父组件的属性或方法;
官方建议useImperativeHandle
和forwardRef
同时使用,减小暴露给父组件的属性,避免使用 ref 这样的命令式代码。
正常状况下 ref 是不能挂在到函数组件上的,由于函数组件没有实例,可是 useImperativeHandle
为咱们提供了一个相似实例的东西。它帮助咱们经过 useImperativeHandle
的第 2 个参数,所返回的对象的内容挂载到 父组件的 ref.current
上。forwardRef
会建立一个React
组件,这个组件可以将其接受的 ref
属性转发到其组件树下的另外一个组件中。
封装Drawer
后,以下:数组
import React, { useState, useImperativeHandle } from 'react'; import styles from './styles'; import Modal from 'react-native-modal'; const Drawer = React.forwardRef((props, ref) => { const [visible, setVisible] = useState(false); const toggleSideMenu = () => { setVisible(!visible); }; useImperativeHandle(ref, () => ({ toggleSideMenu: () => toggleSideMenu() })); return ( <Modal isVisible={visible} onBackdropPress={toggleSideMenu} // Android back press onSwipeComplete={toggleSideMenu} // Swipe to discard animationIn="slideInLeft" // Has others, we want slide in from the left animationOut="slideOutLeft" // When discarding the drawer swipeDirection="left" // Discard the drawer with swipe to left useNativeDriver // Faster animation hideModalContentWhileAnimating // Better performance, try with/without propagateSwipe // Allows swipe events to propagate to children components (eg a ScrollView inside a modal) style={styles.sideMenuStyle} > {props.children} </Modal> ); }); export default Drawer;
父组件使用:ide
import React, { useRef, useState } from 'react'; import { Text, TouchableOpacity, View } from 'react-native'; import styles from './styles'; import { ButtonGroup, Header } from 'react-native-elements'; import common from '../../styles/common'; import SideMenu from './SideMenu'; import Ionicons from 'react-native-vector-icons/Ionicons'; import { useNavigation } from '@react-navigation/native'; import Drawer from '../../components/Drawer'; const ProjectDetail = props => { const { route } = props; console.log('路由参数', route.params); const [selectedIndex, setSelectedIndex] = useState(0); const navigation = useNavigation(); const drawerRef = useRef(); const updateIndex = index => { setSelectedIndex(index); }; const component1 = () => <Text>文件</Text>; const component2 = () => <Text>流程中心</Text>; const buttons = [{ element: component1 }, { element: component2 }]; return ( <View style={common.container}> <Header leftComponent={ <View> <TouchableOpacity onPress={navigation.goBack}> <Ionicons name="arrow-back" size={24} color={'#fff'} /> </TouchableOpacity> </View> } centerComponent={{ text: 'MY TITLE', style: { color: '#fff' } }} rightComponent={ <View> <TouchableOpacity onPress={() => drawerRef.current.toggleSideMenu()} > <Ionicons name="menu" size={24} color={'#fff'} /> </TouchableOpacity> </View> } /> <ButtonGroup onPress={updateIndex} selectedIndex={selectedIndex} buttons={buttons} containerStyle={{ height: 28 }} /> <Drawer ref={drawerRef}> <SideMenu /> </Drawer> </View> ); }; export default ProjectDetail;