Preface
I need a drawer menu on my App. React navigation drawer supports this function, but it changes the structure of the screen. I don’t want to change it because I only need to use this simple drawer menu component on one of the screens. The general effect is as follows:
Installation dependencies
react-native-modal
component can roughly meet my needs. The modal frame plus the animation of moving in and out of left and right plus gestures can basically realize the component of pulling the drawer side by side. Install it now:
yarn add react-native-modal -save
Write SideMenu.js
The SideMenu component is the content displayed in the side menu
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;
Write the main page
Reference components, through the isVisible
parameter control menu display and hide, the toggleSideMenu
method controls the switch display and hide, and there are some parameters that control the entrance animation. To make it closer to the drawer assembly, I use slideInLeft
.
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;
Encapsulation
Because some pages may also need similar drawer components at other times, Modal
component. First of all, we need to click on the function to display the menu externally (possibly in the header of the page), so it must be exposed to the parent The component calls the internal method of the sub-component, then we need forwardRef
and useImperativeHandle
:forwardRef
: Refers to the ref instance of the parent component and becomes a parameter of the child component, which can reference the ref of the parent component and bind it to the node of the child component itself.useImperativeHandle
: The first parameter receives a ref instance that refers to the parent component through forwardRef, and the second parameter is a callback function that returns an object, which stores the properties or methods that need to be exposed to the parent component;
The official recommendation useImperativeHandle
and forwardRef
at the same time to reduce the attributes exposed to the parent component and avoid the use of imperative codes such as ref.
Under normal circumstances, ref cannot be attached to a function component, because the function component does not have an instance, but useImperativeHandle
provides us with something similar to an instance. It helps us through useImperativeHandle
second parameter, the contents of the object returned to the parent mount components ref.current
on.forwardRef
will create a React
component, which can ref
attributes it accepts to another component under its component tree.
After encapsulating Drawer
, it is as follows:
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;
Parent component use:
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;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。