[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:
parent
cbd510bf7d
commit
c700261852
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue