带有路线或导航的 react-native-side-menu 的完整工作示例
Full working example of react-native-side-menu with routes or navigation
我发现很少 post 提供一些 "hints" 如何将 navigation\routes 与 react-native-side-menu 集成,不幸的是没有找到任何 post 显示了此类功能的完整工作示例。
我也不确定 navigation\routes 最简单的实现是什么,这两个选项之间有什么区别(当然这不是专门针对侧边菜单的,但在我的情况下应该结合在一起) .
任何人都可以指出这样的例子吗?
如果您指的是抽屉菜单,请检查 react-native-material-design 和给定的演示应用程序。
所以最终我能够集成标准导航器,如 Facebook 的官方示例代码所示:
https://github.com/facebook/react-native/tree/master/Examples/UIExplorer/Navigator
使用 react-native-side-menu 组件,整个代码是这样的:
'use strict';
var React = require('react');
var ReactNative = require('react-native');
var SideMenu = require('react-native-side-menu');
var {
Component,
Navigator,
AppRegistry,
View,
Text,
Image,
StyleSheet,
ScrollView,
TouchableOpacity
} = ReactNative;
class FirstPage extends Component {
render() {
return (
<View style={styles.page}><Text style={styles.pageContent}>First Page</Text></View>
);
}
}
class FirstPageMenu extends Component {
constructor(props) {
super(props);
this.state = {};
}
toggle() {
this.setState({
isOpen: !this.state.isOpen,
});
}
updateMenuState(isOpen) {
this.setState({ isOpen, });
}
onMenuItemSelected = (item) => {
this.setState({
isOpen: false,
selectedItem: item,
});
this.props.navigator.replace({ id: item });
}
render() {
const menu = <Menu onItemSelected={this.onMenuItemSelected} navigator={this.props.navigator}/>;
return (
<SideMenu
menu={menu}
isOpen={this.state.isOpen}
onChange={(isOpen) => this.updateMenuState(isOpen)}>
<MenuButton onPress={() => this.toggle()}/>
<FirstPage/>
</SideMenu>
);
}
};
class SecondPage extends Component {
...
}
class SecondPageMenu extends Component {
...
}
class ThirdPage extends Component {
...
}
class ThirdPageMenu extends Component {
...
}
class MenuNavigator extends Component {
constructor(props) {
super(props);
this._setNavigatorRef = this._setNavigatorRef.bind(this);
}
renderScene(route, nav) {
switch (route.id) {
case 'first':
return <FirstPageMenu navigator={nav} />;
case 'second':
return <SecondPageMenu navigator={nav} />;
case 'third':
return <ThirdPageMenu navigator={nav} />;
default:
return <FirstPageMenu navigator={nav} />;
}
}
render() {
return (
<Navigator
ref={this._setNavigatorRef}
initialRoute={{id: 'first'}}
renderScene={this.renderScene}
configureScene={(route) => {
if (route.sceneConfig) {
return route.sceneConfig;
}
return Navigator.SceneConfigs.FloatFromBottom;
}}
/>
);
}
componentWillUnmount() {
this._listeners && this._listeners.forEach(listener => listener.remove());
}
_setNavigatorRef(navigator) {
if (navigator !== this._navigator) {
this._navigator = navigator;
if (navigator) {
var callback = (event) => {
console.log(
`NavigatorMenu: event ${event.type}`,
{
route: JSON.stringify(event.data.route),
target: event.target,
type: event.type,
}
);
};
// Observe focus change events from the owner.
this._listeners = [
navigator.navigationContext.addListener('willfocus', callback),
navigator.navigationContext.addListener('didfocus', callback),
];
}
}
}
};
class MenuButton extends Component {
handlePress(e) {
if (this.props.onPress) {
this.props.onPress(e);
}
}
render() {
return (
<View style={styles.menuButton} >
<TouchableOpacity
onPress={this.handlePress.bind(this)}
style={this.props.style}>
<Text>{this.props.children}</Text>
<Image
source={{ uri: 'http://i.imgur.com/vKRaKDX.png', width: 40, height: 40, }} />
</TouchableOpacity>
</View>
);
}
}
class Menu extends Component {
static propTypes = {
onItemSelected: React.PropTypes.func.isRequired,
};
constructor(props) {
super(props);
}
render() {
return (
<ScrollView scrollsToTop={false} style={styles.menu}>
<Text
onPress={() => this.props.onItemSelected('first')}
style={styles.item}>
First
</Text>
<Text
onPress={() => this.props.onItemSelected('second')}
style={styles.item}>
Second
</Text>
<Text
onPress={() => this.props.onItemSelected('third')}
style={styles.item}>
Third
</Text>
</ScrollView>
);
}
};
var styles = StyleSheet.create({
menuButton: {
marginTop: 20,
backgroundColor: '#777'
},
menu: {
flex: 1,
width: window.width,
height: window.height,
padding: 20,
},
item: {
fontSize: 16,
fontWeight: '300',
paddingTop: 20,
},
page: {
flex: 1,
alignItems: 'center',
backgroundColor: '#777'
},
pageContent: {
flex: 1,
alignItems: 'center',
top: 200,
},
menu: {
flex: 1,
width: window.width,
height: window.height,
padding: 20,
},
item: {
fontSize: 16,
fontWeight: '300',
paddingTop: 20,
},
});
module.exports = MenuNavigator;
索引文件应该指向导航器:
const React = require('react-native');
const { AppRegistry, } = React;
const MenuNavigator = require('./SideMenuWithNavigation');
AppRegistry.registerComponent('MyApp', () => MenuNavigator);
我的 Github 上有一个开胃菜 react-native-side-menu
和 navigator
。
This starter also use redux (won't prevent from answering how to deal navigator + side menu).
使用侧边菜单时,routing
的 trick
将替换之前的路线以防止其堆叠(就像在普通导航中一样):
navigate(route) {
const routeStack = [].concat(this.refs.navigator.getCurrentRoutes());
const previousRouteId = routeStack[routeStack.length - 1].id;
if (route.id !== previousRouteId) {
this.refs.navigator.replace(route);
}
if (this.state.sideMenuOpened) {
this.closeSideMenu();
}
}
检查我的启动器reactNativeReduxFastStarter
代码快速预览:
import React, {
Component
} from 'react';
import {
StyleSheet,
Text,
Dimensions,
Navigator,
StatusBar
} from 'react-native';
import SideMenu from 'react-native-side-menu';
import Icon from 'react-native-vector-icons/Ionicons';
import { AppRoutes } from '../../../common/config';
import {
SideMenuContent,
Button
} from '../../components';
import Home from '../home';
import AppState from '../appState';
const SCREEN_WIDTH = Dimensions.get('window').width;
class App extends Component {
constructor(props) {
super(props);
this.init();
}
init() {
this.state = {
sideMenuOpened: false
};
}
openSideMenu() {
this.setState({
sideMenuOpened : false
});
}
closeSideMenu() {
if (this.state.sideMenuOpened) {
this.setState({
sideMenuOpened : false
});
}
}
toggleSideMenu() {
this.setState({
sideMenuOpened: !this.state.sideMenuOpened
});
}
updateSideMenuState(isOpened) {
this.setState({
sideMenuOpened: isOpened
});
}
navigate(route) {
const routeStack = [].concat(this.refs.navigator.getCurrentRoutes());
const previousRouteId = routeStack[routeStack.length - 1].id;
if (route.id !== previousRouteId) {
this.refs.navigator.replace(route);
}
if (this.state.sideMenuOpened) {
this.closeSideMenu();
}
}
renderScene(route, navigator) {
switch (route.id) {
case 1:
const route1 = AppRoutes.getRouteFromRouteId(1);
return (
<Home
ref={route1.refView}
navigator={navigator}
navigate={(toRoute)=>this.navigate(toRoute)}
/>
);
case 2:
const route2 = AppRoutes.getRouteFromRouteId(2);
return (
<AppState
ref={route2.refView}
navigator={navigator}
navigate={(toRoute)=>this.navigate(toRoute)}
/>
);
default:
return (
<Home
ref={route1.refView}
navigator={navigator}
navigate={(toRoute)=>this.navigate(toRoute)}
/>
);
}
}
renderRouteMapper() {
const routes = AppRoutes.getAllRoutes();
return {
Title : (route, navigator, index, navState) => {
const currentRouteId = navState.routeStack[index].id;
return (
<Text style={styles.titleNavText}>
{routes[currentRouteId - 1].navbar.navBarTitle}
</Text>
);
},
LeftButton : (route, navigator, index, navState) => {
const currentRouteId = navState.routeStack[index].id;
return (
<Button
style={styles.leftNavButton}
onPress={(e)=>this.toggleSideMenu(e)
}>
<Icon
name={routes[currentRouteId - 1].navbar.navBarLeftIconName}
size={32}
color={'#333333'}
/>
</Button>
);
},
RightButton : (route, navigator, index, navState) => {
return null;
}
};
}
render() {
StatusBar.setBarStyle('light-content', true);
const DEFAULT_ROUTE = { id: 1, refView: 'HomeView' };
return (
<SideMenu
menu={<SideMenuContent
backGndColor="#ECECEC"
navigate={(route)=>this.navigate(route)}
/>}
isOpen={this.state.sideMenuOpened}
onChange={(isOpened) => this.updateSideMenuState(isOpened)}
bounceBackOnOverdraw={false}
openMenuOffset={SCREEN_WIDTH * 0.8}
>
<Navigator
ref="navigator"
initialRoute={ DEFAULT_ROUTE }
sceneStyle={ styles.navigator }
renderScene={(route, navigator)=>this.renderScene(route, navigator)}
configureScene={()=>Navigator.SceneConfigs.FadeAndroid}
navigationBar={
<Navigator.NavigationBar
routeMapper={this.renderRouteMapper()}
style={styles.navBar}
/>
}
/>
</SideMenu>
);
}
}
const styles = StyleSheet.create({
navigator: {
backgroundColor: '#fff',
borderLeftWidth: 0.5,
borderLeftColor: '#F1F1F1',
},
navBar: {
backgroundColor: '#fff',
borderWidth: 0.5,
borderColor: '#F1F1F1'
},
leftNavButton : {
flex : 1,
flexDirection : 'column',
alignItems : 'center',
marginTop : 4,
paddingTop : 0,
paddingBottom : 10,
paddingLeft : 20,
paddingRight : 10
},
rightNavButton : {
flex : 1,
flexDirection : 'column',
alignItems : 'center',
marginTop : 4,
paddingTop : 6,
paddingBottom : 10,
paddingLeft : 10,
paddingRight : 10
},
titleNavText : {
marginTop : 14,
color : '#333333'
}
});
export default App;
您可以在 github 上查看这个完整的侧边菜单项目。本项目包含ToolbarAndroid、routes、DrawerLayoutAndroid、溢出菜单等组件
我发现很少 post 提供一些 "hints" 如何将 navigation\routes 与 react-native-side-menu 集成,不幸的是没有找到任何 post 显示了此类功能的完整工作示例。
我也不确定 navigation\routes 最简单的实现是什么,这两个选项之间有什么区别(当然这不是专门针对侧边菜单的,但在我的情况下应该结合在一起) .
任何人都可以指出这样的例子吗?
如果您指的是抽屉菜单,请检查 react-native-material-design 和给定的演示应用程序。
所以最终我能够集成标准导航器,如 Facebook 的官方示例代码所示:
https://github.com/facebook/react-native/tree/master/Examples/UIExplorer/Navigator
使用 react-native-side-menu 组件,整个代码是这样的:
'use strict';
var React = require('react');
var ReactNative = require('react-native');
var SideMenu = require('react-native-side-menu');
var {
Component,
Navigator,
AppRegistry,
View,
Text,
Image,
StyleSheet,
ScrollView,
TouchableOpacity
} = ReactNative;
class FirstPage extends Component {
render() {
return (
<View style={styles.page}><Text style={styles.pageContent}>First Page</Text></View>
);
}
}
class FirstPageMenu extends Component {
constructor(props) {
super(props);
this.state = {};
}
toggle() {
this.setState({
isOpen: !this.state.isOpen,
});
}
updateMenuState(isOpen) {
this.setState({ isOpen, });
}
onMenuItemSelected = (item) => {
this.setState({
isOpen: false,
selectedItem: item,
});
this.props.navigator.replace({ id: item });
}
render() {
const menu = <Menu onItemSelected={this.onMenuItemSelected} navigator={this.props.navigator}/>;
return (
<SideMenu
menu={menu}
isOpen={this.state.isOpen}
onChange={(isOpen) => this.updateMenuState(isOpen)}>
<MenuButton onPress={() => this.toggle()}/>
<FirstPage/>
</SideMenu>
);
}
};
class SecondPage extends Component {
...
}
class SecondPageMenu extends Component {
...
}
class ThirdPage extends Component {
...
}
class ThirdPageMenu extends Component {
...
}
class MenuNavigator extends Component {
constructor(props) {
super(props);
this._setNavigatorRef = this._setNavigatorRef.bind(this);
}
renderScene(route, nav) {
switch (route.id) {
case 'first':
return <FirstPageMenu navigator={nav} />;
case 'second':
return <SecondPageMenu navigator={nav} />;
case 'third':
return <ThirdPageMenu navigator={nav} />;
default:
return <FirstPageMenu navigator={nav} />;
}
}
render() {
return (
<Navigator
ref={this._setNavigatorRef}
initialRoute={{id: 'first'}}
renderScene={this.renderScene}
configureScene={(route) => {
if (route.sceneConfig) {
return route.sceneConfig;
}
return Navigator.SceneConfigs.FloatFromBottom;
}}
/>
);
}
componentWillUnmount() {
this._listeners && this._listeners.forEach(listener => listener.remove());
}
_setNavigatorRef(navigator) {
if (navigator !== this._navigator) {
this._navigator = navigator;
if (navigator) {
var callback = (event) => {
console.log(
`NavigatorMenu: event ${event.type}`,
{
route: JSON.stringify(event.data.route),
target: event.target,
type: event.type,
}
);
};
// Observe focus change events from the owner.
this._listeners = [
navigator.navigationContext.addListener('willfocus', callback),
navigator.navigationContext.addListener('didfocus', callback),
];
}
}
}
};
class MenuButton extends Component {
handlePress(e) {
if (this.props.onPress) {
this.props.onPress(e);
}
}
render() {
return (
<View style={styles.menuButton} >
<TouchableOpacity
onPress={this.handlePress.bind(this)}
style={this.props.style}>
<Text>{this.props.children}</Text>
<Image
source={{ uri: 'http://i.imgur.com/vKRaKDX.png', width: 40, height: 40, }} />
</TouchableOpacity>
</View>
);
}
}
class Menu extends Component {
static propTypes = {
onItemSelected: React.PropTypes.func.isRequired,
};
constructor(props) {
super(props);
}
render() {
return (
<ScrollView scrollsToTop={false} style={styles.menu}>
<Text
onPress={() => this.props.onItemSelected('first')}
style={styles.item}>
First
</Text>
<Text
onPress={() => this.props.onItemSelected('second')}
style={styles.item}>
Second
</Text>
<Text
onPress={() => this.props.onItemSelected('third')}
style={styles.item}>
Third
</Text>
</ScrollView>
);
}
};
var styles = StyleSheet.create({
menuButton: {
marginTop: 20,
backgroundColor: '#777'
},
menu: {
flex: 1,
width: window.width,
height: window.height,
padding: 20,
},
item: {
fontSize: 16,
fontWeight: '300',
paddingTop: 20,
},
page: {
flex: 1,
alignItems: 'center',
backgroundColor: '#777'
},
pageContent: {
flex: 1,
alignItems: 'center',
top: 200,
},
menu: {
flex: 1,
width: window.width,
height: window.height,
padding: 20,
},
item: {
fontSize: 16,
fontWeight: '300',
paddingTop: 20,
},
});
module.exports = MenuNavigator;
索引文件应该指向导航器:
const React = require('react-native');
const { AppRegistry, } = React;
const MenuNavigator = require('./SideMenuWithNavigation');
AppRegistry.registerComponent('MyApp', () => MenuNavigator);
我的 Github 上有一个开胃菜 react-native-side-menu
和 navigator
。
This starter also use redux (won't prevent from answering how to deal navigator + side menu).
使用侧边菜单时,routing
的 trick
将替换之前的路线以防止其堆叠(就像在普通导航中一样):
navigate(route) {
const routeStack = [].concat(this.refs.navigator.getCurrentRoutes());
const previousRouteId = routeStack[routeStack.length - 1].id;
if (route.id !== previousRouteId) {
this.refs.navigator.replace(route);
}
if (this.state.sideMenuOpened) {
this.closeSideMenu();
}
}
检查我的启动器reactNativeReduxFastStarter
代码快速预览:
import React, {
Component
} from 'react';
import {
StyleSheet,
Text,
Dimensions,
Navigator,
StatusBar
} from 'react-native';
import SideMenu from 'react-native-side-menu';
import Icon from 'react-native-vector-icons/Ionicons';
import { AppRoutes } from '../../../common/config';
import {
SideMenuContent,
Button
} from '../../components';
import Home from '../home';
import AppState from '../appState';
const SCREEN_WIDTH = Dimensions.get('window').width;
class App extends Component {
constructor(props) {
super(props);
this.init();
}
init() {
this.state = {
sideMenuOpened: false
};
}
openSideMenu() {
this.setState({
sideMenuOpened : false
});
}
closeSideMenu() {
if (this.state.sideMenuOpened) {
this.setState({
sideMenuOpened : false
});
}
}
toggleSideMenu() {
this.setState({
sideMenuOpened: !this.state.sideMenuOpened
});
}
updateSideMenuState(isOpened) {
this.setState({
sideMenuOpened: isOpened
});
}
navigate(route) {
const routeStack = [].concat(this.refs.navigator.getCurrentRoutes());
const previousRouteId = routeStack[routeStack.length - 1].id;
if (route.id !== previousRouteId) {
this.refs.navigator.replace(route);
}
if (this.state.sideMenuOpened) {
this.closeSideMenu();
}
}
renderScene(route, navigator) {
switch (route.id) {
case 1:
const route1 = AppRoutes.getRouteFromRouteId(1);
return (
<Home
ref={route1.refView}
navigator={navigator}
navigate={(toRoute)=>this.navigate(toRoute)}
/>
);
case 2:
const route2 = AppRoutes.getRouteFromRouteId(2);
return (
<AppState
ref={route2.refView}
navigator={navigator}
navigate={(toRoute)=>this.navigate(toRoute)}
/>
);
default:
return (
<Home
ref={route1.refView}
navigator={navigator}
navigate={(toRoute)=>this.navigate(toRoute)}
/>
);
}
}
renderRouteMapper() {
const routes = AppRoutes.getAllRoutes();
return {
Title : (route, navigator, index, navState) => {
const currentRouteId = navState.routeStack[index].id;
return (
<Text style={styles.titleNavText}>
{routes[currentRouteId - 1].navbar.navBarTitle}
</Text>
);
},
LeftButton : (route, navigator, index, navState) => {
const currentRouteId = navState.routeStack[index].id;
return (
<Button
style={styles.leftNavButton}
onPress={(e)=>this.toggleSideMenu(e)
}>
<Icon
name={routes[currentRouteId - 1].navbar.navBarLeftIconName}
size={32}
color={'#333333'}
/>
</Button>
);
},
RightButton : (route, navigator, index, navState) => {
return null;
}
};
}
render() {
StatusBar.setBarStyle('light-content', true);
const DEFAULT_ROUTE = { id: 1, refView: 'HomeView' };
return (
<SideMenu
menu={<SideMenuContent
backGndColor="#ECECEC"
navigate={(route)=>this.navigate(route)}
/>}
isOpen={this.state.sideMenuOpened}
onChange={(isOpened) => this.updateSideMenuState(isOpened)}
bounceBackOnOverdraw={false}
openMenuOffset={SCREEN_WIDTH * 0.8}
>
<Navigator
ref="navigator"
initialRoute={ DEFAULT_ROUTE }
sceneStyle={ styles.navigator }
renderScene={(route, navigator)=>this.renderScene(route, navigator)}
configureScene={()=>Navigator.SceneConfigs.FadeAndroid}
navigationBar={
<Navigator.NavigationBar
routeMapper={this.renderRouteMapper()}
style={styles.navBar}
/>
}
/>
</SideMenu>
);
}
}
const styles = StyleSheet.create({
navigator: {
backgroundColor: '#fff',
borderLeftWidth: 0.5,
borderLeftColor: '#F1F1F1',
},
navBar: {
backgroundColor: '#fff',
borderWidth: 0.5,
borderColor: '#F1F1F1'
},
leftNavButton : {
flex : 1,
flexDirection : 'column',
alignItems : 'center',
marginTop : 4,
paddingTop : 0,
paddingBottom : 10,
paddingLeft : 20,
paddingRight : 10
},
rightNavButton : {
flex : 1,
flexDirection : 'column',
alignItems : 'center',
marginTop : 4,
paddingTop : 6,
paddingBottom : 10,
paddingLeft : 10,
paddingRight : 10
},
titleNavText : {
marginTop : 14,
color : '#333333'
}
});
export default App;
您可以在 github 上查看这个完整的侧边菜单项目。本项目包含ToolbarAndroid、routes、DrawerLayoutAndroid、溢出菜单等组件