前言
我的 App 应用程序上须要一个抽屉菜单。React navigation drawer 导航反对此性能,然而扭转了屏幕的构造,我不心愿更改, 因为我只是其中一个屏幕上须要用到这个简略抽屉菜单组件。大抵的成果如下:
装置依赖
react-native-modal
组件大抵能够满足我的需要,模态框加上左右移入移出的动画加上手势根本能实现侧拉抽屉的组件。当初装置它:
yarn add react-native-modal -save
编写 SideMenu.js
SideMenu 组件是侧拉菜单外面展现的内容
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
。
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;
父组件应用:
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;