[RN] Refactor SideBar layout and animation

Layout:

Use an absolute-fill view as the background with the sidebar on top of. This
greatly simplifies styling, as there is no need to calculate how large the
backdrop needs to be.

Animation:

Switch to a translateX transform animation. This serves 2 purposes: first,
there seems to be a bug somewhere in React Native 0.51-0.55 where the content
that is being animated starts to be clipped. Very weird! But more importantly,
translateX transmorm animations are supported by the native animation driver!

https://facebook.github.io/react-native/blog/2017/02/14/using-native-driver-for-animated.html
8f5ebe5952/Libraries/Animated/src/NativeAnimatedHelper.js (L138-L176)

This makes the animation more performant and buttery smooth.

Some small cleanups are also included here.
This commit is contained in:
Saúl Ibarra Corretgé 2018-05-25 11:57:32 +02:00 committed by Lyubo Marinov
parent cbd510bf7d
commit c700261852
2 changed files with 48 additions and 91 deletions

View File

@ -1,9 +1,8 @@
// @flow
import React, { Component } from 'react';
import React, { Component, type Node } from 'react';
import {
Animated,
Dimensions,
TouchableWithoutFeedback,
View
} from 'react-native';
@ -15,15 +14,10 @@ import styles, { SIDEBAR_WIDTH } from './styles';
*/
type Props = {
/**
* The local participant's avatar
*/
_avatar: string,
/**
* The children of the Component
*/
children: React$Node,
children: Node,
/**
* Callback to notify the containing Component that the sidebar is
@ -47,11 +41,6 @@ type State = {
*/
showOverlay: boolean,
/**
* Indicates whether the side bar is visible or not.
*/
showSideBar: boolean,
/**
* The native animation object.
*/
@ -62,8 +51,6 @@ type State = {
* A generic animated side bar to be used for left side menus
*/
export default class SideBar extends Component<Props, State> {
_mounted: boolean;
/**
* Initializes a new {@code SideBar} instance.
*
@ -74,15 +61,10 @@ export default class SideBar extends Component<Props, State> {
this.state = {
showOverlay: false,
showSideBar: false,
sliderAnimation: new Animated.Value(-SIDEBAR_WIDTH)
sliderAnimation: new Animated.Value(0)
};
this._getContainerStyle = this._getContainerStyle.bind(this);
this._onHideMenu = this._onHideMenu.bind(this);
this._setShow = this._setShow.bind(this);
this._setShow(props.show);
}
/**
@ -91,7 +73,7 @@ export default class SideBar extends Component<Props, State> {
* @inheritdoc
*/
componentDidMount() {
this._mounted = true;
this._setShow(this.props.show);
}
/**
@ -112,42 +94,37 @@ export default class SideBar extends Component<Props, State> {
*/
render() {
return (
<Animated.View
style = { this._getContainerStyle() } >
<View style = { styles.sideMenuContent }>
<View
pointerEvents = 'box-none'
style = { styles.sideMenuContainer } >
{
this.props.children
}
</View>
<TouchableWithoutFeedback
onPress = { this._onHideMenu }
style = { styles.sideMenuShadowTouchable } >
this.state.showOverlay
&& <TouchableWithoutFeedback
onPress = { this._onHideMenu } >
<View style = { styles.sideMenuShadow } />
</TouchableWithoutFeedback>
}
<Animated.View style = { this._getContentStyle() }>
{ this.props.children }
</Animated.View>
</View>
);
}
_getContainerStyle: () => Array<Object>
_getContentStyle: () => Array<Object>;
/**
* Assembles a style array for the container.
* Assembles a style array for the sidebar content.
*
* @private
* @returns {Array<Object>}
*/
_getContainerStyle() {
_getContentStyle() {
const { sliderAnimation } = this.state;
const { height, width } = Dimensions.get('window');
const transformStyle
= { transform: [ { translateX: sliderAnimation } ] };
return [
styles.sideMenuContainer,
{
left: sliderAnimation,
width: this.state.showOverlay
? Math.max(height, width) + SIDEBAR_WIDTH : SIDEBAR_WIDTH
}
];
return [ styles.sideMenuContent, transformStyle ];
}
_onHideMenu: () => void;
@ -163,9 +140,7 @@ export default class SideBar extends Component<Props, State> {
const { onHide } = this.props;
if (typeof onHide === 'function') {
onHide();
}
onHide && onHide();
}
_setShow: (boolean) => void;
@ -178,30 +153,21 @@ export default class SideBar extends Component<Props, State> {
* @returns {void}
*/
_setShow(show) {
if (this.state.showSideBar !== show) {
if (show) {
this.setState({
showOverlay: true
});
this.setState({ showOverlay: true });
}
Animated
.timing(
this.state.sliderAnimation,
{ toValue: show ? 0 : -SIDEBAR_WIDTH })
{
toValue: show ? SIDEBAR_WIDTH : 0,
useNativeDriver: true
})
.start(animationState => {
if (animationState.finished && !show) {
this.setState({
showOverlay: false
});
this.setState({ showOverlay: false });
}
});
}
if (this._mounted) {
this.setState({
showSideBar: show
});
}
}
}

View File

@ -278,8 +278,14 @@ const SIDEBAR_STYLES = {
* The topmost container of the side bar.
*/
sideMenuContainer: {
...StyleSheet.absoluteFillObject
},
/**
* The container of the actual content of the side menu.
*/
sideMenuContent: {
bottom: 0,
flexDirection: 'row',
left: -SIDEBAR_WIDTH,
position: 'absolute',
top: 0,
@ -287,27 +293,12 @@ const SIDEBAR_STYLES = {
},
/**
* The container of the actual content of the side menu.
*/
sideMenuContent: {
width: SIDEBAR_WIDTH
},
/**
* The opaque area that covers the rest of the scren, when
* The opaque area that covers the rest of the screen, when
* the side bar is open.
*/
sideMenuShadow: {
backgroundColor: 'rgba(0, 0, 0, 0.5)',
flex: 1
},
/**
* The touchable area of the rest of the screen that closes the side bar
* when tapped.
*/
sideMenuShadowTouchable: {
flex: 1
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0, 0, 0, 0.5)'
}
};