Reputation: 17264
I've an app that needs Drawer
& Navigator
. To achieve this, I've a Drawer component as parent in index.ios.js which encapsulates everything else.
Now the use case that I'm trying to solve is, I've to disable Drawer or hide NavigationBar in some routes. To achieve that, I'm updating parent component's state & re-rendering on every navigation. However, that doesn't seem to help.
index.io.js :
class MyApp extends Component{
state = {drawerEnabled:true, navigationBarEnabled: true};
constructor(){
super();
this.navigate = this.navigate.bind(this);
this.navigateForDrawer = this.navigateForDrawer.bind(this);
this.getNavigator = this.getNavigator.bind(this);
this.renderScene = this.renderScene.bind(this);
}
navigateForDrawer(route) {
this.refs.navigator.resetTo(route);
this.refs.drawer.close();
}
getNavigationBar(){
return (
this.state.navigationBarEnabled ?
<Navigator.NavigationBar
style={styles.navBar}
transitionStyles={Navigator.NavigationBar.TransitionStylesIOS}
routeMapper={NavigationBarRouteMapper}
/> : null);
}
getNavigator(){
return (
<Navigator
ref="navigator"
style={styles.navigator}
initialRoute={{id:'Login'}}
renderScene={this.renderScene}
navigationBar={this.getNavigationBar()}
/>
);
}
navigate(route, method){
if(route)
switch (route.id) {
case 'Login':
this.state = {drawerEnabled: false, navigationBarEnabled: false};
break;
case 'NewBooking':
this.state = {drawerEnabled: true, navigationBarEnabled: true};
break;
case 'MyBookings':
this.state = {drawerEnabled: true, navigationBarEnabled: true};
break;
case 'AboutUs':
this.state = {drawerEnabled: true, navigationBarEnabled: true};
break;
case 'ConfirmBooking':
this.state = {drawerEnabled: false, navigationBarEnabled: true};
break;
case 'BookingComplete':
this.state = {drawerEnabled: false, navigationBarEnabled: true};
break;
}
this.forceUpdate();
this.refs.navigator[method](route);
}
renderScene(route, navigator){
navigator.navigate = this.navigate;
switch (route.id) {
case 'Login':
return <Login navigate={this.navigate}/>;
case 'NewBooking':
route.title = 'New Booking';
return <NewBooking navigate={this.navigate}/>;
case 'MyBookings':
route.title = 'My Bookings';
return <MyBookings navigate={this.navigate}/>;
case 'AboutUs':
route.title = 'About Us';
return <AboutUs navigate={this.navigate}/>;
case 'ConfirmBooking':
route.title = 'Booking Summary';
return <ConfirmBooking navigate={this.navigate} booking={route.booking}/>;
case 'BookingComplete':
route.title = 'Booking Complete';
return <BookingComplete navigate={this.navigate} booking={route.booking}/>;
default:
}
}
render() {
return (
<Drawer
ref="drawer"
disabled={!this.state.drawerEnabled}
content={<ControlPanel navigate={this.navigateForDrawer}/>}
tapToClose={true}
openDrawerOffset={0.2} // 20% gap on the right side of drawer
panCloseMask={0.2}
closedDrawerOffset={-3}
styles={{
main: {paddingLeft: 3}
}}
tweenHandler={(ratio) => ({
main: { opacity:(2-ratio)/2 }
})}
>
{this.getNavigator()}
</Drawer>
)
}
}
To Navigate to a new route :
this.props.navigate({id: 'ConfirmBooking', booking: booking}, 'push');
PS: The drawer that I'm using is react-native-drawer
Upvotes: 3
Views: 6376
Reputation: 364
I had to do the same thing with a navigator bar appearing on certain pages and I did the following:
First I wrote my own NavBar which is simply a screen with flex:1
and has two inner components. The first is the Navigation bar which has flex:1
and contains three inner components, left button, title and right button with flex:1, flex:2, flex:1
respectively. This way I can have a custom navigation bar on each screen.
Here's the code:
React.createClass({
propTypes: {
/* Specifies the background color of the navigation bar*/
backgroundColor: React.PropTypes.string,
/* The view to be displayed */
view: React.PropTypes.element,
/* A component representing the left button */
leftButton: React.PropTypes.element,
/* Title component that shows up in the middle of the screen */
title: React.PropTypes.element,
/* Right button component */
rightButton: React.PropTypes.element,
/* including a modal ensures that the modal is centered in the screen */
modal: React.PropTypes.element,
/* Callback that is called from the Navigator bar screen. Any arguments
* passed, represent the positioning and dimensions of the Navigation Bar
*/
onLayout: React.PropTypes.func,
/*The separator that shows up between the Navigation Bar and the view*/
separator: React.PropTypes.element,
},
render(){
return (
<View style={styles.modalWrapper}>
<View
style={styles.container}>
<View
style={[styles.navBar, {backgroundColor: this.props.backgroundColor}]}
onLayout={(e) =>{
this.props.onLayout && this.props.onLayout(e)
}}>
<View
style={styles.leftButton}>
{this.props.leftButton}
</View>
<View
style={styles.title}>
{this.props.title }
</View>
<View
style={styles.rightButton}>
{this.props.rightButton}
</View>
</View>
<View style={styles.viewWrapper}>
{this.props.separator }
{this.props.view}
</View>
</View>
{this.props.modal}
</View>);
},
});
And the styles:
StyleSheet.create({
modalWrapper: {
width: device.width,
height: device.height
},
container: {
flex: 1,
flexDirection: 'column',
},
navBar: {
flex: 1,
flexDirection: 'row',
alignItems: 'center'
},
viewWrapper: {
flex: 8
},
rightButton: {
top: STATUS_BAR_OFFSET,
flex: 1,
alignItems: 'flex-end',
justifyContent: 'center',
paddingRight: PADDING,
},
leftButton: {
top: STATUS_BAR_OFFSET,
flex: 1,
alignItems: 'center',
justifyContent: 'flex-start',
paddingLeft: PADDING,
},
title: {
top: STATUS_BAR_OFFSET,
flex: 2,
alignItems: 'center',
justifyContent: 'center',
paddingRight: PADDING / 2,
paddingLeft: PADDING / 2,
}
});
Next, while I haven't had a change to try this yet, I would suggest usint onStartShouldSetResponderCapture
. If you try and run the following example you'll see the order that nested views capture touch events. Under react native onStartShouldSetResponder
starts at the lowest child view and the the touch events bubble up to the top if no child responds. However, onStartShouldSetResponderCapture
is called first on the outer most view.
React.createClass({
render() {
return (
<View onStartShouldSetResponderCapture={() => this.text('1')}
onStartShouldSetResponder={() => this.text('4')}
style={{flex:1}}>
<View onStartShouldSetResponderCapture={() => this.text('2')}
onStartShouldSetResponder={() => this.text('3')}
style={{flex:1}}>
</View>
</View>
);
},
text(text) {
console.log(text);
//return true; // comment this to see the order or stop at '1'
}
});
so in your case I would do the following:
render() {
return (
<View onStartShouldSetResponderCapture={() => !this.state.drawerEnabled}>
<Drawer
ref="drawer"
disabled={!this.state.drawerEnabled}
content={<ControlPanel navigate={this.navigateForDrawer}/>}
tapToClose={true}
openDrawerOffset={0.2} // 20% gap on the right side of drawer
panCloseMask={0.2}
closedDrawerOffset={-3}
styles={{
main: {paddingLeft: 3}
}}
tweenHandler={(ratio) => ({
main: { opacity:(2-ratio)/2 }
})}
>
{this.getNavigator()}
</Drawer>
</View>
);
}
Finally, rather than calling forceUpdate()
I would call this.setState({drawerEnabled: true})
. Also, don't forget to bind in your constructor.
Upvotes: 1
Reputation: 11921
I assume that you mean DrawerLayoutAndroid (or DrawerLayout) as drawer component.
You could simply use the following line in your code to programmatically open or close the drawer on route change:
this.refs.drawer.openDrawer(); // opens the drawer
this.refs.drawer.closeDrawer(); // closes the drawer
I would include these calls in your navigate function, this seems to be the best place.
Upvotes: 3