個人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;