[IMPROVEMENT] Use Reanimated's Transitions instead of LayoutAnimation (#1206)

* [IMPROVEMENT] Use Reanimated's Transitions instead of LayoutAnimation

* Don't run on Android

* Refactor

* Remove unnecessary code
This commit is contained in:
Diego Mello 2019-09-19 10:32:24 -03:00 committed by GitHub
parent b29a2ab216
commit 81198b4c4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 96 additions and 186 deletions

View File

@ -1,77 +0,0 @@
import { View, Animated } from 'react-native';
import PropTypes from 'prop-types';
import React from 'react';
export default class Panel extends React.Component {
static propTypes = {
open: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
style: PropTypes.object
}
constructor(props) {
super(props);
this.state = {
animation: new Animated.Value()
};
this.first = true;
this.open = false;
this.opacity = 0;
}
componentDidMount() {
const { animation } = this.state;
const { open } = this.props;
const initialValue = !open ? this.height : 0;
animation.setValue(initialValue);
}
componentWillReceiveProps(nextProps) {
const { animation } = this.state;
const { open } = this.props;
if (this.first) {
this.first = false;
if (!open) {
animation.setValue(0);
return;
}
}
if (this.open === nextProps.open) {
return;
}
this.open = nextProps.open;
const initialValue = !nextProps.open ? this.height : 0;
const finalValue = !nextProps.open ? 0 : this.height;
animation.setValue(initialValue);
Animated.timing(
animation,
{
toValue: finalValue,
duration: 150,
useNativeDriver: true
}
).start();
}
set _height(h) {
this.height = h || this.height;
}
render() {
const { animation } = this.state;
const { style, children } = this.props;
return (
<Animated.View
style={[{ height: animation }, style]}
>
<View onLayout={({ nativeEvent }) => this._height = nativeEvent.layout.height} style={{ position: !this.first ? 'relative' : 'absolute' }}>
{children}
</View>
</Animated.View>
);
}
}

View File

@ -1,63 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import { Animated, Text } from 'react-native';
export default class Fade extends React.Component {
static propTypes = {
visible: PropTypes.bool.isRequired,
style: Animated.View.propTypes.style,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node
])
}
constructor(props) {
super(props);
const { visible } = this.props;
this.state = {
visible
};
this._visibility = new Animated.Value(visible ? 1 : 0);
}
componentWillReceiveProps(nextProps) {
if (nextProps.visible) {
this.setState({ visible: true });
}
Animated.timing(this._visibility, {
toValue: nextProps.visible ? 1 : 0,
duration: 300,
useNativeDriver: true
}).start(() => {
this.setState({ visible: nextProps.visible });
});
}
render() {
const { visible } = this.state;
const { style, children, ...rest } = this.props;
const containerStyle = {
opacity: this._visibility.interpolate({
inputRange: [0, 1],
outputRange: [0, 1]
}),
transform: [
{
scale: this._visibility.interpolate({
inputRange: [0, 1],
outputRange: [1.1, 1]
})
}
]
};
const combinedStyle = [containerStyle, style];
return (
<Animated.View style={visible ? combinedStyle : containerStyle} {...rest}>
<Text>{visible ? children : null}</Text>
</Animated.View>
);
}
}

View File

@ -19,6 +19,7 @@ import { defaultHeader, onNavigationStateChange } from './utils/navigation';
import { loggerConfig, analytics } from './utils/log'; import { loggerConfig, analytics } from './utils/log';
import Toast from './containers/Toast'; import Toast from './containers/Toast';
import RocketChat from './lib/rocketchat'; import RocketChat from './lib/rocketchat';
import LayoutAnimation from './utils/layoutAnimation';
useScreens(); useScreens();
@ -308,12 +309,14 @@ export default class Root extends React.Component {
render() { render() {
return ( return (
<Provider store={store}> <Provider store={store}>
<App <LayoutAnimation>
ref={(navigatorRef) => { <App
Navigation.setTopLevelNavigator(navigatorRef); ref={(navigatorRef) => {
}} Navigation.setTopLevelNavigator(navigatorRef);
onNavigationStateChange={onNavigationStateChange} }}
/> onNavigationStateChange={onNavigationStateChange}
/>
</LayoutAnimation>
</Provider> </Provider>
); );
} }

View File

@ -11,6 +11,7 @@ import sharedStyles from './views/Styles';
import { isNotch, isIOS } from './utils/deviceInfo'; import { isNotch, isIOS } from './utils/deviceInfo';
import { defaultHeader, onNavigationStateChange } from './utils/navigation'; import { defaultHeader, onNavigationStateChange } from './utils/navigation';
import RocketChat from './lib/rocketchat'; import RocketChat from './lib/rocketchat';
import LayoutAnimation from './utils/layoutAnimation';
const InsideNavigator = createStackNavigator({ const InsideNavigator = createStackNavigator({
ShareListView: { ShareListView: {
@ -84,12 +85,14 @@ class Root extends React.Component {
onLayout={this.handleLayout} onLayout={this.handleLayout}
> >
<Provider store={store}> <Provider store={store}>
<AppContainer <LayoutAnimation>
ref={(navigatorRef) => { <AppContainer
Navigation.setTopLevelNavigator(navigatorRef); ref={(navigatorRef) => {
}} Navigation.setTopLevelNavigator(navigatorRef);
onNavigationStateChange={onNavigationStateChange} }}
/> onNavigationStateChange={onNavigationStateChange}
/>
</LayoutAnimation>
</Provider> </Provider>
</View> </View>
); );

View File

@ -0,0 +1,44 @@
import React from 'react';
import { Transition, Transitioning } from 'react-native-reanimated';
import PropTypes from 'prop-types';
import debounce from './debounce';
import { isIOS } from './deviceInfo';
import sharedStyles from '../views/Styles';
const transition = (
<Transition.Together>
<Transition.In type='fade' />
<Transition.Out type='fade' />
<Transition.Change interpolation='easeInOut' />
</Transition.Together>
);
const TRANSITION_REF = React.createRef();
export const animateNextTransition = debounce(() => {
if (isIOS) {
TRANSITION_REF.current.animateNextTransition();
}
}, 200, true);
const LayoutAnimation = ({ children }) => {
if (isIOS) {
return (
<Transitioning.View
style={sharedStyles.root}
transition={transition}
ref={TRANSITION_REF}
>
{children}
</Transitioning.View>
);
}
return children;
};
LayoutAnimation.propTypes = {
children: PropTypes.node
};
export default LayoutAnimation;

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import {
Keyboard, Text, ScrollView, View, StyleSheet, Alert, LayoutAnimation Keyboard, Text, ScrollView, View, StyleSheet, Alert
} from 'react-native'; } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { SafeAreaView } from 'react-navigation'; import { SafeAreaView } from 'react-navigation';
@ -18,6 +18,7 @@ import { loginRequest as loginRequestAction } from '../actions/login';
import { LegalButton } from '../containers/HeaderButton'; import { LegalButton } from '../containers/HeaderButton';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
import { COLOR_PRIMARY } from '../constants/colors'; import { COLOR_PRIMARY } from '../constants/colors';
import { animateNextTransition } from '../utils/layoutAnimation';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
bottomContainer: { bottomContainer: {
@ -84,7 +85,7 @@ class LoginView extends React.Component {
this.setTitle(nextProps.Site_Name); this.setTitle(nextProps.Site_Name);
} else if (nextProps.failure && !equal(error, nextProps.error)) { } else if (nextProps.failure && !equal(error, nextProps.error)) {
if (nextProps.error && nextProps.error.error === 'totp-required') { if (nextProps.error && nextProps.error.error === 'totp-required') {
LayoutAnimation.easeInEaseOut(); animateNextTransition();
this.setState({ showTOTP: true }); this.setState({ showTOTP: true });
return; return;
} }

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import {
Text, ScrollView, Keyboard, Image, StyleSheet, TouchableOpacity, View, Alert, LayoutAnimation Text, ScrollView, Keyboard, Image, StyleSheet, TouchableOpacity, View, Alert
} from 'react-native'; } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { SafeAreaView } from 'react-navigation'; import { SafeAreaView } from 'react-navigation';
@ -23,6 +23,7 @@ import { CustomIcon } from '../lib/Icons';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
import { COLOR_PRIMARY } from '../constants/colors'; import { COLOR_PRIMARY } from '../constants/colors';
import log from '../utils/log'; import log from '../utils/log';
import { animateNextTransition } from '../utils/layoutAnimation';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
image: { image: {
@ -195,7 +196,7 @@ class NewServerView extends React.Component {
uriToPath = uri => uri.replace('file://', ''); uriToPath = uri => uri.replace('file://', '');
saveCertificate = (certificate) => { saveCertificate = (certificate) => {
LayoutAnimation.easeInEaseOut(); animateNextTransition();
this.setState({ certificate }); this.setState({ certificate });
} }

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { import {
ActivityIndicator, FlatList, InteractionManager, LayoutAnimation ActivityIndicator, FlatList, InteractionManager
} from 'react-native'; } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
@ -15,6 +15,7 @@ import RocketChat from '../../lib/rocketchat';
import log from '../../utils/log'; import log from '../../utils/log';
import EmptyRoom from './EmptyRoom'; import EmptyRoom from './EmptyRoom';
import { isIOS } from '../../utils/deviceInfo'; import { isIOS } from '../../utils/deviceInfo';
import { animateNextTransition } from '../../utils/layoutAnimation';
export class List extends React.Component { export class List extends React.Component {
static propTypes = { static propTypes = {
@ -83,7 +84,7 @@ export class List extends React.Component {
} }
const messages = orderBy(data, ['ts'], ['desc']); const messages = orderBy(data, ['ts'], ['desc']);
if (this.mounted) { if (this.mounted) {
LayoutAnimation.easeInEaseOut(); animateNextTransition();
this.setState({ messages }); this.setState({ messages });
} else { } else {
this.state.messages = messages; this.state.messages = messages;
@ -186,11 +187,11 @@ export class List extends React.Component {
style={styles.list} style={styles.list}
inverted inverted
removeClippedSubviews={isIOS} removeClippedSubviews={isIOS}
initialNumToRender={7} // initialNumToRender={7}
onEndReached={this.onEndReached} onEndReached={this.onEndReached}
onEndReachedThreshold={5} // onEndReachedThreshold={5}
maxToRenderPerBatch={5} // maxToRenderPerBatch={5}
windowSize={10} // windowSize={10}
ListFooterComponent={this.renderFooter} ListFooterComponent={this.renderFooter}
{...scrollPersistTaps} {...scrollPersistTaps}
/> />

View File

@ -1,8 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import { Text, View, InteractionManager } from 'react-native';
Text, View, InteractionManager, LayoutAnimation
} from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { RectButton } from 'react-native-gesture-handler'; import { RectButton } from 'react-native-gesture-handler';
import { SafeAreaView } from 'react-navigation'; import { SafeAreaView } from 'react-navigation';
@ -484,15 +482,11 @@ class RoomView extends React.Component {
if (!this.mounted) { if (!this.mounted) {
return; return;
} }
if (isIOS && this.beginAnimating) {
LayoutAnimation.easeInEaseOut();
}
this.setState(...args); this.setState(...args);
} }
sendMessage = (message, tmid) => { sendMessage = (message, tmid) => {
const { user } = this.props; const { user } = this.props;
LayoutAnimation.easeInEaseOut();
RocketChat.sendMessage(this.rid, message, this.tmid || tmid, user).then(() => { RocketChat.sendMessage(this.rid, message, this.tmid || tmid, user).then(() => {
this.setLastOpen(null); this.setLastOpen(null);
}); });

View File

@ -8,7 +8,6 @@ import {
Text, Text,
ScrollView, ScrollView,
Keyboard, Keyboard,
LayoutAnimation,
Dimensions Dimensions
} from 'react-native'; } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -43,6 +42,7 @@ import {
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import ListHeader from './ListHeader'; import ListHeader from './ListHeader';
import { selectServerRequest as selectServerRequestAction } from '../../actions/server'; import { selectServerRequest as selectServerRequestAction } from '../../actions/server';
import { animateNextTransition } from '../../utils/layoutAnimation';
const SCROLL_OFFSET = 56; const SCROLL_OFFSET = 56;
@ -290,8 +290,8 @@ class RoomsListView extends React.Component {
// eslint-disable-next-line react/sort-comp // eslint-disable-next-line react/sort-comp
internalSetState = (...args) => { internalSetState = (...args) => {
const { navigation } = this.props; const { navigation } = this.props;
if (isIOS && navigation.isFocused()) { if (navigation.isFocused()) {
LayoutAnimation.easeInEaseOut(); animateNextTransition();
} }
this.setState(...args); this.setState(...args);
}; };

View File

@ -1,8 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import { View, StyleSheet, FlatList } from 'react-native';
View, StyleSheet, FlatList, LayoutAnimation
} from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { SafeAreaView } from 'react-navigation'; import { SafeAreaView } from 'react-navigation';
import equal from 'deep-equal'; import equal from 'deep-equal';
@ -25,6 +23,7 @@ import sharedStyles from './Styles';
import { Item, CustomHeaderButtons } from '../containers/HeaderButton'; import { Item, CustomHeaderButtons } from '../containers/HeaderButton';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
import { COLOR_WHITE } from '../constants/colors'; import { COLOR_WHITE } from '../constants/colors';
import { animateNextTransition } from '../utils/layoutAnimation';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
safeAreaView: { safeAreaView: {
@ -169,7 +168,7 @@ class SelectedUsersView extends React.Component {
toggleUser = (user) => { toggleUser = (user) => {
const { addUser, removeUser } = this.props; const { addUser, removeUser } = this.props;
LayoutAnimation.easeInEaseOut(); animateNextTransition();
if (!this.isChecked(user.name)) { if (!this.isChecked(user.name)) {
addUser(user); addUser(user);
} else { } else {

View File

@ -1,8 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import { Keyboard, View, StyleSheet } from 'react-native';
Keyboard, LayoutAnimation, View, StyleSheet
} from 'react-native';
import ShareExtension from 'rn-extensions-share'; import ShareExtension from 'rn-extensions-share';
import SearchBox from '../../../containers/SearchBox'; import SearchBox from '../../../containers/SearchBox';
@ -10,6 +8,7 @@ import { CloseShareExtensionButton } from '../../../containers/HeaderButton';
import { HEADER_BACKGROUND } from '../../../constants/colors'; import { HEADER_BACKGROUND } from '../../../constants/colors';
import sharedStyles from '../../Styles'; import sharedStyles from '../../Styles';
import { animateNextTransition } from '../../../utils/layoutAnimation';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -33,12 +32,12 @@ const Header = React.memo(({
Keyboard.dismiss(); Keyboard.dismiss();
onChangeText(''); onChangeText('');
cancelSearch(); cancelSearch();
LayoutAnimation.easeInEaseOut(); animateNextTransition();
}; };
const onFocus = () => { const onFocus = () => {
initSearch(); initSearch();
LayoutAnimation.easeInEaseOut(); animateNextTransition();
}; };
return ( return (

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import {
View, Text, LayoutAnimation, FlatList, ActivityIndicator, Keyboard, BackHandler View, Text, FlatList, ActivityIndicator, Keyboard, BackHandler
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-navigation'; import { SafeAreaView } from 'react-navigation';
import ShareExtension from 'rn-extensions-share'; import ShareExtension from 'rn-extensions-share';
@ -25,6 +25,7 @@ import ShareListHeader from './Header';
import styles from './styles'; import styles from './styles';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import { animateNextTransition } from '../../utils/layoutAnimation';
const LIMIT = 50; const LIMIT = 50;
const getItemLayout = (data, index) => ({ length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index }); const getItemLayout = (data, index) => ({ length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index });
@ -174,8 +175,8 @@ class ShareListView extends React.Component {
// eslint-disable-next-line react/sort-comp // eslint-disable-next-line react/sort-comp
internalSetState = (...args) => { internalSetState = (...args) => {
const { navigation } = this.props; const { navigation } = this.props;
if (isIOS && navigation.isFocused()) { if (navigation.isFocused()) {
LayoutAnimation.easeInEaseOut(); animateNextTransition();
} }
this.setState(...args); this.setState(...args);
} }

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import {
ScrollView, Text, View, FlatList, LayoutAnimation, SafeAreaView ScrollView, Text, View, FlatList, SafeAreaView
} from 'react-native'; } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import equal from 'deep-equal'; import equal from 'deep-equal';
@ -20,6 +20,7 @@ import styles from './styles';
import SidebarItem from './SidebarItem'; import SidebarItem from './SidebarItem';
import { COLOR_TEXT } from '../../constants/colors'; import { COLOR_TEXT } from '../../constants/colors';
import database from '../../lib/database'; import database from '../../lib/database';
import { animateNextTransition } from '../../utils/layoutAnimation';
const keyExtractor = item => item.id; const keyExtractor = item => item.id;
@ -147,7 +148,7 @@ class Sidebar extends Component {
} }
toggleStatus = () => { toggleStatus = () => {
LayoutAnimation.easeInEaseOut(); animateNextTransition();
this.setState(prevState => ({ showStatus: !prevState.showStatus })); this.setState(prevState => ({ showStatus: !prevState.showStatus }));
} }

View File

@ -5,6 +5,9 @@ import {
} from '../constants/colors'; } from '../constants/colors';
export default StyleSheet.create({ export default StyleSheet.create({
root: {
flex: 1
},
container: { container: {
backgroundColor: 'white', backgroundColor: 'white',
flex: 1 flex: 1