From 36120ad0ec4e918f81beda1b13ec5cce861ecab7 Mon Sep 17 00:00:00 2001 From: Terry Sahaidak Date: Mon, 18 Feb 2019 22:23:48 +0200 Subject: [PATCH] Fix using multiple animations --- .eslintrc | 3 +- Example/App.js | 6 +- Example/app.json | 3 + Example/navigation/AppNavigator.js | 5 + Example/screens/Keyframes/Keyframes.js | 25 ++- .../screens/TransitionBase/TransitionBase.js | 32 ++-- lib/components/DelegateAnimation.js | 3 +- lib/components/KeyframesAnimation.js | 26 +-- lib/components/Reanimatable.js | 4 +- lib/components/ScrollView.js | 2 +- lib/components/TransitionAnimation.js | 31 ++-- lib/core/animations.js | 2 +- lib/core/createConfig.js | 6 +- lib/core/createDelegateAnimation.js | 4 +- lib/core/createKeyframesAnimation.js | 67 ++++--- lib/core/createTransitionAnimation.js | 170 +++++++++--------- 16 files changed, 227 insertions(+), 162 deletions(-) diff --git a/.eslintrc b/.eslintrc index 3c72e87..65fbdda 100644 --- a/.eslintrc +++ b/.eslintrc @@ -38,6 +38,7 @@ "class-methods-use-this": "off", "arrow-parens": "off", "import/no-unresolved": ["error", { "ignore": ["^react$", "^react-native$", "^react-native-reanimated$"] }], - "import/extensions": ["error", "never", {"ignorePackages": true, "js": "never"} ] + "import/extensions": ["error", "never", {"ignorePackages": true, "js": "never"} ], + "react/no-multi-comp": 0 } } diff --git a/Example/App.js b/Example/App.js index 75994b7..bb8d938 100644 --- a/Example/App.js +++ b/Example/App.js @@ -1,4 +1,5 @@ import React from 'react'; +import { StatusBar } from 'react-native'; import AppNavigator from './navigation/AppNavigator'; import { NavigationService } from './services'; // import screens from './navigation/screens'; @@ -12,7 +13,10 @@ class App extends React.PureComponent { render() { return ( - NavigationService.init(ref)} /> + + + NavigationService.init(ref)} /> + ); } } diff --git a/Example/app.json b/Example/app.json index dd3f189..46cc804 100644 --- a/Example/app.json +++ b/Example/app.json @@ -24,6 +24,9 @@ ], "ios": { "supportsTablet": true + }, + "androidStatusBar": { + "barStyle": "dark-content" } } } \ No newline at end of file diff --git a/Example/navigation/AppNavigator.js b/Example/navigation/AppNavigator.js index db2abce..48056d5 100644 --- a/Example/navigation/AppNavigator.js +++ b/Example/navigation/AppNavigator.js @@ -2,6 +2,7 @@ import { createStackNavigator, createAppContainer, } from 'react-navigation'; +import { StatusBar, Platform } from 'react-native'; import screens from './screens'; import Examples from '../screens/Examples/Examples'; import TransitionBase from '../screens/TransitionBase/TransitionBase'; @@ -18,6 +19,10 @@ const AppNavigator = createStackNavigator( { defaultNavigationOptions: ({ navigation }) => ({ title: navigation.getParam('title'), + headerStyle: { + marginTop: + Platform.OS === 'ios' ? null : -StatusBar.currentHeight, + }, }), }, ); diff --git a/Example/screens/Keyframes/Keyframes.js b/Example/screens/Keyframes/Keyframes.js index 1259eb3..3f36c60 100644 --- a/Example/screens/Keyframes/Keyframes.js +++ b/Example/screens/Keyframes/Keyframes.js @@ -62,7 +62,7 @@ const config = createAnimationConfig({ const s = StyleSheet.create({ container: { - flex: 1, + height: 300, backgroundColor: colors.white, paddingTop: 50, }, @@ -90,7 +90,7 @@ const s = StyleSheet.create({ }, }); -export default class App extends React.PureComponent { +class Example extends React.PureComponent { state = { value: false, }; @@ -134,3 +134,24 @@ export default class App extends React.PureComponent { ); } } + +export default class App extends React.PureComponent { + state = { + showSecond: false, + }; + + componentDidMount() { + // testing each animation has its own state + setTimeout(() => this.setState({ showSecond: true }), 1000); + } + + render() { + return ( + + + + {this.state.showSecond && } + + ); + } +} diff --git a/Example/screens/TransitionBase/TransitionBase.js b/Example/screens/TransitionBase/TransitionBase.js index 8232b9f..dea8da9 100644 --- a/Example/screens/TransitionBase/TransitionBase.js +++ b/Example/screens/TransitionBase/TransitionBase.js @@ -1,5 +1,5 @@ import React from 'react'; -import { StyleSheet, View, Dimensions } from 'react-native'; +import { StyleSheet, View, Dimensions, FlatList } from 'react-native'; import { Reanimatable, createAnimationConfig, @@ -29,27 +29,23 @@ const config = createAnimationConfig({ }); const s = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: colors.white, - paddingTop: 50, + scroll: { + paddingVertical: 20, }, animationContainer: { - marginBottom: 100, + height: 250, + justifyContent: 'center', }, animatableView: { - height: 100, backgroundColor: colors.red, }, row: { flexDirection: 'row', - position: 'absolute', - bottom: 50, alignSelf: 'center', }, }); -export default class App extends React.PureComponent { +class Example extends React.PureComponent { state = { value: true, }; @@ -67,7 +63,7 @@ export default class App extends React.PureComponent { render() { return ( - + } + keyExtractor={(_, i) => i.toString()} + /> + ); +} diff --git a/lib/components/DelegateAnimation.js b/lib/components/DelegateAnimation.js index fa9b998..c5491b0 100644 --- a/lib/components/DelegateAnimation.js +++ b/lib/components/DelegateAnimation.js @@ -5,7 +5,7 @@ class DelegateAnimation extends React.PureComponent { constructor(props) { super(props); - const { values } = props._internal; + const { values } = props.generate(); this.state = { values, @@ -19,6 +19,7 @@ class DelegateAnimation extends React.PureComponent { DelegateAnimation.propTypes = { children: T.func, + generate: T.func, }; export default DelegateAnimation; diff --git a/lib/components/KeyframesAnimation.js b/lib/components/KeyframesAnimation.js index 960283d..d4d0851 100644 --- a/lib/components/KeyframesAnimation.js +++ b/lib/components/KeyframesAnimation.js @@ -1,4 +1,5 @@ import React from 'react'; +import { InteractionManager } from 'react-native'; import T from 'prop-types'; import A from 'react-native-reanimated'; @@ -6,19 +7,22 @@ class KeyframesAnimation extends React.PureComponent { constructor(props) { super(props); - const { values, animation, operations } = props._internal; + const { values, operations } = props.generate(); this._operations = operations; this.state = { values, - animation, }; } - componentWillUnmount() { - if (this.props.resetOnUnmount) { - setTimeout(() => this.reset(), 16); - } + componentDidMount() { + InteractionManager.runAfterInteractions(() => { + const animation = this._operations.createAnimation(); + + this.setState({ + animation, + }); + }); } reset() { @@ -28,7 +32,9 @@ class KeyframesAnimation extends React.PureComponent { render() { return ( - + {this.state.animation && ( + + )} {this.props.children(this.state.values)} ); @@ -37,11 +43,7 @@ class KeyframesAnimation extends React.PureComponent { KeyframesAnimation.propTypes = { children: T.func, - resetOnUnmount: T.bool, -}; - -KeyframesAnimation.defaultProps = { - resetOnUnmount: true, + generate: T.func, }; export default KeyframesAnimation; diff --git a/lib/components/Reanimatable.js b/lib/components/Reanimatable.js index c0ee3ef..61b169e 100644 --- a/lib/components/Reanimatable.js +++ b/lib/components/Reanimatable.js @@ -10,7 +10,7 @@ import { import { ANIMATION_TYPE } from '../core/constants'; const validateConfig = (config) => { - if (typeof config._internal !== 'object') { + if (typeof config.generate !== 'function') { throw new Error( 'Invalid config provided for the Reanimatable component. Did you forget to use "createAnimationConfig"?', ); @@ -63,7 +63,7 @@ const Reanimatable = React.forwardRef(({ break; } - if (this.containerStyle) { + if (containerStyle) { return {content}; } diff --git a/lib/components/ScrollView.js b/lib/components/ScrollView.js index 39987fc..8261917 100644 --- a/lib/components/ScrollView.js +++ b/lib/components/ScrollView.js @@ -74,7 +74,7 @@ class ScrollView extends React.PureComponent { ScrollView.propTypes = { delegate: T.shape({ - event: T.func, + event: T.object, reset: T.func, }), onScroll: T.func, diff --git a/lib/components/TransitionAnimation.js b/lib/components/TransitionAnimation.js index f19de44..0e91f02 100644 --- a/lib/components/TransitionAnimation.js +++ b/lib/components/TransitionAnimation.js @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import { InteractionManager } from 'react-native'; import T from 'prop-types'; import A from 'react-native-reanimated'; import { ANIMATION_STATE } from '../core/constants'; @@ -7,14 +8,13 @@ class TransitionAnimation extends Component { constructor(props) { super(props); + this.initialValue = props.value; + const { - values, animationState, - animation, operations, - } = props._internal; - - this.initialValue = props.value; + values, + } = this.props.generate(); this._operations = operations; @@ -22,7 +22,6 @@ class TransitionAnimation extends Component { this.state = { values, - animation, }; } @@ -31,6 +30,14 @@ class TransitionAnimation extends Component { // we have to do it on a mounted component // the user won't notice it this.reset(); + + InteractionManager.runAfterInteractions(() => { + const animation = this._operations.createAnimation(); + + this.setState({ + animation, + }); + }); } componentDidUpdate(prevProps) { @@ -43,10 +50,6 @@ class TransitionAnimation extends Component { } } - componentWillUnmount() { - this.reset(); - } - reset() { this.resetTo(this.initialValue); } @@ -63,7 +66,9 @@ class TransitionAnimation extends Component { render() { return ( - + {this.state.animation && ( + + )} {this.props.children(this.state.values)} ); @@ -71,11 +76,9 @@ class TransitionAnimation extends Component { } TransitionAnimation.propTypes = { - values: T.object, - animation: T.object, value: T.bool, children: T.func, - duration: T.number, + generate: T.func, }; export default TransitionAnimation; diff --git a/lib/core/animations.js b/lib/core/animations.js index b06d4aa..a85bc53 100644 --- a/lib/core/animations.js +++ b/lib/core/animations.js @@ -25,7 +25,7 @@ export const runTiming = ({ return A.block([ // stop opposite clock before running our animation // to set last (previous) position as a current one - A.cond(A.clockRunning(oppositeClock), A.stopClock(oppositeClock)), + oppositeClock ? A.cond(A.clockRunning(oppositeClock), A.stopClock(oppositeClock)) : 0, // run our animation clock A.cond( A.clockRunning(clock), diff --git a/lib/core/createConfig.js b/lib/core/createConfig.js index a234b13..1d38ee9 100644 --- a/lib/core/createConfig.js +++ b/lib/core/createConfig.js @@ -8,13 +8,13 @@ export default function createConfig(animationConfig) { if (animationConfig.animation.delegate) { config.type = ANIMATION_TYPE.DELEGATE; - config._internal = createDelegateAnimation(animationConfig); + config.generate = createDelegateAnimation(animationConfig); } else if (animationConfig.keyframes) { config.type = ANIMATION_TYPE.KEYFRAMES; - config._internal = createKeyframesAnimation(animationConfig); + config.generate = createKeyframesAnimation(animationConfig); } else { config.type = ANIMATION_TYPE.TRANSITION; - config._internal = createTransitionAnimation(animationConfig); + config.generate = createTransitionAnimation(animationConfig); } return config; diff --git a/lib/core/createDelegateAnimation.js b/lib/core/createDelegateAnimation.js index b2afaa6..5bfba98 100644 --- a/lib/core/createDelegateAnimation.js +++ b/lib/core/createDelegateAnimation.js @@ -69,7 +69,7 @@ export default function createDelegateAnimation(config) { baseValue: delegate.value, }); - return { + return () => ({ values, - }; + }); } diff --git a/lib/core/createKeyframesAnimation.js b/lib/core/createKeyframesAnimation.js index 72edba2..d11f06b 100644 --- a/lib/core/createKeyframesAnimation.js +++ b/lib/core/createKeyframesAnimation.js @@ -37,15 +37,20 @@ function normalizeValues(keyframes) { }, {}); } -function generateInterpolations({ keyframes, duration, baseValue }) { +function createRanges(keyframes, duration) { const normalized = normalizeValues(keyframes); - return Object.keys(normalized).reduce((acc, name) => { + return Object.keys(normalized).map((name) => { const pairs = normalized[name]; + // pairs = [[frameName, value], [frameName, value]] + return [name, generateRanges(pairs, duration)]; + }); +} +function generateInterpolations({ ranges, duration, baseValue }) { + return ranges.reduce((acc, [name, range]) => { const animatedValue = A.interpolate(baseValue, { - // pairs = [[frameName, value], [frameName, value]] - ...generateRanges(pairs, duration), + ...range, extrapolate: A.Extrapolate.CLAMP, }); @@ -61,33 +66,37 @@ export default function createKeyframesAnimation(config) { animation: { duration }, } = config; - const interpolation = new A.Value(0); - const clock = new A.Clock(); - - const animation = runTiming({ - clock, - oppositeClock: new A.Clock(), - value: interpolation, - dest: duration, - duration, - }); + const ranges = createRanges(keyframes, duration); - const values = generateInterpolations({ - keyframes, - duration, - baseValue: interpolation, - }); + return () => { + const interpolation = new A.Value(0); - function reset() { - interpolation.setValue(0); - } + function reset() { + interpolation.setValue(0); + } - return { - interpolation, - animation, - values, - operations: { - reset, - }, + const values = generateInterpolations({ + ranges, + duration, + baseValue: interpolation, + }); + const clock = new A.Clock(); + + const createAnimation = () => + runTiming({ + clock, + value: interpolation, + dest: duration, + duration, + }); + + return { + interpolation, + values, + operations: { + createAnimation, + reset, + }, + }; }; } diff --git a/lib/core/createTransitionAnimation.js b/lib/core/createTransitionAnimation.js index 13e8c68..ae9c06d 100644 --- a/lib/core/createTransitionAnimation.js +++ b/lib/core/createTransitionAnimation.js @@ -2,8 +2,8 @@ import A from 'react-native-reanimated'; import { ANIMATION_STATE } from './constants'; import { runTiming } from './animations'; -function createValues(values) { - return Object.keys(values).reduce((acc, valueName) => { +function createValues(valueNames) { + return valueNames.reduce((acc, valueName) => { acc[valueName] = new A.Value(0); return acc; }, {}); @@ -32,87 +32,93 @@ function getProperAnimation(reanimatableConfig, animationConfig) { } export default function createTransitionAnimation(config) { - const values = createValues(config.values, config.initialValue); - const animationState = new A.Value(ANIMATION_STATE.START_POINT); - - const { forwardAnimations, backwardAnimations } = Object.keys( - config.values, - ).reduce( - (acc, key, index) => { - const animation = config.values[key]; - const { from, to } = animation; - const currentValue = values[key]; - - const first = index === 0; - - const forwardAnimationClock = new A.Clock(); - const backwardAnimationClock = new A.Clock(); - - const forwardAnimationConfig = { - clock: forwardAnimationClock, - oppositeClock: backwardAnimationClock, - value: currentValue, - dest: to, - }; - - const backwardAnimationConfig = { - clock: backwardAnimationClock, - oppositeClock: forwardAnimationClock, - value: currentValue, - dest: from, - }; - - if (first) { - forwardAnimationConfig.onFinish = A.block([ - A.set(animationState, ANIMATION_STATE.END_POINT), - ]); - - backwardAnimationConfig.onFinish = A.block([ - A.set(animationState, ANIMATION_STATE.START_POINT), - ]); - } - - const forwardTiming = getProperAnimation( - config, - forwardAnimationConfig, - ); - const backwardTiming = getProperAnimation( - config, - backwardAnimationConfig, + const valueNames = Object.keys(config.values); + return () => { + const values = createValues(valueNames); + const animationState = new A.Value(ANIMATION_STATE.START_POINT); + + function createAnimation() { + const { + forwardAnimations, + backwardAnimations, + } = valueNames.reduce( + (acc, key, index) => { + const animation = config.values[key]; + const { from, to } = animation; + const currentValue = values[key]; + + const first = index === 0; + + const forwardAnimationClock = new A.Clock(); + const backwardAnimationClock = new A.Clock(); + + const forwardAnimationConfig = { + clock: forwardAnimationClock, + oppositeClock: backwardAnimationClock, + value: currentValue, + dest: to, + }; + + const backwardAnimationConfig = { + clock: backwardAnimationClock, + oppositeClock: forwardAnimationClock, + value: currentValue, + dest: from, + }; + + if (first) { + forwardAnimationConfig.onFinish = A.block([ + A.set(animationState, ANIMATION_STATE.END_POINT), + ]); + + backwardAnimationConfig.onFinish = A.block([ + A.set(animationState, ANIMATION_STATE.START_POINT), + ]); + } + + const forwardTiming = getProperAnimation( + config, + forwardAnimationConfig, + ); + const backwardTiming = getProperAnimation( + config, + backwardAnimationConfig, + ); + + acc.forwardAnimations.push(forwardTiming); + + acc.backwardAnimations.push(backwardTiming); + + return acc; + }, + { + forwardAnimations: [], + backwardAnimations: [], + }, ); - acc.forwardAnimations.push(forwardTiming); - - acc.backwardAnimations.push(backwardTiming); - - return acc; - }, - { - forwardAnimations: [], - backwardAnimations: [], - }, - ); - - const animation = A.block([ - A.cond( - A.eq(animationState, ANIMATION_STATE.PLAY_FORWARD), - // run all the forward animations - A.block(forwardAnimations), - ), - - A.cond( - A.eq(animationState, ANIMATION_STATE.PLAY_BACKWARD), - // run all the backward animations - A.block(backwardAnimations), - ), - ]); - - return { - animation, - values, - animationState, - operations: { - reset: setValues(config.values, values), - }, + return A.block([ + A.cond( + A.eq(animationState, ANIMATION_STATE.PLAY_FORWARD), + // run all the forward animations + A.block(forwardAnimations), + ), + + A.cond( + A.eq(animationState, ANIMATION_STATE.PLAY_BACKWARD), + // run all the backward animations + A.block(backwardAnimations), + ), + ]); + } + + return { + values, + animationState, + operations: { + createAnimation, + reset: setValues(config.values, values), + }, + }; }; }