From 0a6876374385f50369ffd50eb07cfa04dd8ae20b Mon Sep 17 00:00:00 2001 From: Eli White Date: Thu, 1 Aug 2019 16:42:42 -0700 Subject: [PATCH] Add explicit `useNativeDriver: false` to callsites Summary: In order to cleanup the callsites that are not using Animated's native driver, we are going to make useNativeDriver a required option so people have to think about whether they want the native driver or not. I made this change by changing [Animated.js](https://fburl.com/ritcebri) to have this animation config type: ``` export type AnimationConfig = { isInteraction?: boolean, useNativeDriver: true, onComplete?: ?EndCallback, iterations?: number, }; ``` This causes Flow to error anywhere where useNativeDriver isn't set or where it is set to false. I then used these Flow errors to codemod the callsites. I got the location of the Flow errors by running: ``` flow status --strip-root --json --message-width=0 | jq '.errors | [.[].extra | .[].message | .[].loc | objects | {source: .source, start: .start, end: .end}]' ``` And then ran this codemod: ``` const json = JSON.parse('JSON RESULT FROM FLOW'); const fileLookup = new Map(); json.forEach(item => { if (!fileLookup.has(item.source)) { fileLookup.set(item.source, []); } fileLookup.get(item.source).push(item); }); export default function transformer(file, api) { const j = api.jscodeshift; const filePath = file.path; if (!fileLookup.has(filePath)) { return; } const locationInfo = fileLookup.get(filePath); return j(file.source) .find(j.ObjectExpression) .forEach(path => { if ( path.node.properties.some( property => property != null && property.key != null && property.key.name === 'useNativeDriver', ) ) { return; } const hasErrorOnLine = locationInfo.some( singleLocationInfo => singleLocationInfo.start.line === path.node.loc.start.line && Math.abs( singleLocationInfo.start.column - path.node.loc.start.column, ) <= 2, ); if (!hasErrorOnLine) { return; } path.node.properties.push( j.property( 'init', j.identifier('useNativeDriver'), j.booleanLiteral(false), ), ); }) .toSource(); } export const parser = 'flow'; ``` ``` yarn jscodeshift --parser=flow --transform addUseNativeDriver.js RKJSModules react-native-github ``` Followed up with ``` hg status -n --change . | xargs js1 prettier ``` Reviewed By: mdvacca Differential Revision: D16611291 fbshipit-source-id: 1157587416ec7603d1a59e1fad6a821f1f57b952 --- .../Animated/src/AnimatedImplementation.js | 7 +- .../js/examples/Animated/AnimatedExample.js | 73 +++++++++++++++---- .../Animated/AnimatedGratuitousApp/AnExApp.js | 26 +++++-- .../AnimatedGratuitousApp/AnExBobble.js | 24 ++++-- .../AnimatedGratuitousApp/AnExChained.js | 13 +++- .../Animated/AnimatedGratuitousApp/AnExSet.js | 7 +- .../AnimatedGratuitousApp/AnExTilt.js | 29 ++++++-- .../js/examples/Touchable/TouchableExample.js | 6 +- .../js/examples/Transform/TransformExample.js | 1 + 9 files changed, 151 insertions(+), 35 deletions(-) diff --git a/Libraries/Animated/src/AnimatedImplementation.js b/Libraries/Animated/src/AnimatedImplementation.js index 6195d9be811..5ab98a5ce89 100644 --- a/Libraries/Animated/src/AnimatedImplementation.js +++ b/Libraries/Animated/src/AnimatedImplementation.js @@ -403,7 +403,12 @@ const parallel = function( const delay = function(time: number): CompositeAnimation { // Would be nice to make a specialized implementation - return timing(new AnimatedValue(0), {toValue: 0, delay: time, duration: 0}); + return timing(new AnimatedValue(0), { + toValue: 0, + delay: time, + duration: 0, + useNativeDriver: false, + }); }; const stagger = function( diff --git a/RNTester/js/examples/Animated/AnimatedExample.js b/RNTester/js/examples/Animated/AnimatedExample.js index 409c4f00b08..f11a5065a86 100644 --- a/RNTester/js/examples/Animated/AnimatedExample.js +++ b/RNTester/js/examples/Animated/AnimatedExample.js @@ -57,8 +57,13 @@ exports.examples = [ // Uses easing functions this.state.fadeAnim, // The value to drive { - toValue: 1, // Target - duration: 2000, // Configuration + // Target + toValue: 1, + + // Configuration + duration: 2000, + + useNativeDriver: false, }, ).start(); // Don't forget start! } @@ -121,10 +126,19 @@ exports.examples = [ { Animated.spring(this.anim, { - toValue: 0, // Returns to the start - velocity: 3, // Velocity makes it move - tension: -10, // Slow - friction: 1, // Oscillate a lot + // Returns to the start + toValue: 0, + + // Velocity makes it move + velocity: 3, + + // Slow + tension: -10, + + // Oscillate a lot + friction: 1, + + useNativeDriver: false, }).start(); }}> Press to Fling it! @@ -182,18 +196,35 @@ exports.examples = [ timing(this.anims[0], { toValue: 200, easing: Easing.linear, + useNativeDriver: false, }), Animated.delay(400), // Use with sequence timing(this.anims[0], { toValue: 0, - easing: Easing.elastic(2), // Springy + + // Springy + easing: Easing.elastic(2), + + useNativeDriver: false, }), Animated.delay(400), Animated.stagger( 200, this.anims - .map(anim => timing(anim, {toValue: 200})) - .concat(this.anims.map(anim => timing(anim, {toValue: 0}))), + .map(anim => + timing(anim, { + toValue: 200, + useNativeDriver: false, + }), + ) + .concat( + this.anims.map(anim => + timing(anim, { + toValue: 0, + useNativeDriver: false, + }), + ), + ), ), Animated.delay(400), Animated.parallel( @@ -206,6 +237,7 @@ exports.examples = [ toValue: 320, easing, duration: 3000, + useNativeDriver: false, }), ), ), @@ -215,8 +247,12 @@ exports.examples = [ this.anims.map(anim => timing(anim, { toValue: 0, - easing: Easing.bounce, // Like a ball + + // Like a ball + easing: Easing.bounce, + duration: 2000, + useNativeDriver: false, }), ), ), @@ -250,10 +286,19 @@ exports.examples = [ { Animated.spring(this.anim, { - toValue: 0, // Returns to the start - velocity: 3, // Velocity makes it move - tension: -10, // Slow - friction: 1, // Oscillate a lot + // Returns to the start + toValue: 0, + + // Velocity makes it move + velocity: 3, + + // Slow + tension: -10, + + // Oscillate a lot + friction: 1, + + useNativeDriver: false, }).start(); }}> Press to Spin it! diff --git a/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExApp.js b/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExApp.js index d01c1e95ff2..e14bdbfe481 100644 --- a/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExApp.js +++ b/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExApp.js @@ -48,8 +48,13 @@ class Circle extends React.Component { this.props.onMove && this.props.onMove(value); }); Animated.spring(this.state.pop, { - toValue: 1, // Pop to larger size. (step2b: uncomment) - ...config, // Reuse config for convenient consistency (step2b: uncomment) + // Pop to larger size. (step2b: uncomment) + toValue: 1, + + // Reuse config for convenient consistency (step2b: uncomment) + ...config, + + useNativeDriver: false, }).start(); this.setState( { @@ -61,8 +66,11 @@ class Circle extends React.Component { onPanResponderRelease: (e, gestureState) => { LayoutAnimation.easeInEaseOut(); // @flowfixme animates layout update as one batch (step3: uncomment) Animated.spring(this.state.pop, { - toValue: 0, // Pop back to 0 (step2c: uncomment) + // Pop back to 0 (step2c: uncomment) + toValue: 0, + ...config, + useNativeDriver: false, }).start(); this.setState({panResponder: undefined}); this.props.onMove && @@ -185,7 +193,11 @@ class Circle extends React.Component { _toggleIsActive(velocity) { const config = {tension: 30, friction: 7}; if (this.state.isActive) { - Animated.spring(this.props.openVal, {toValue: 0, ...config}).start(() => { + Animated.spring(this.props.openVal, { + toValue: 0, + ...config, + useNativeDriver: false, + }).start(() => { // (step4: uncomment) this.setState({isActive: false}, this.props.onDeactivate); }); // (step4: uncomment) @@ -193,7 +205,11 @@ class Circle extends React.Component { this.props.onActivate(); this.setState({isActive: true, panResponder: undefined}, () => { // this.props.openVal.setValue(1); // (step4: comment) - Animated.spring(this.props.openVal, {toValue: 1, ...config}).start(); // (step4: uncomment) + Animated.spring(this.props.openVal, { + toValue: 1, + ...config, + useNativeDriver: false, + }).start(); // (step4: uncomment) }); } } diff --git a/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExBobble.js b/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExBobble.js index 0654be84a4a..a2c95aadd08 100644 --- a/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExBobble.js +++ b/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExBobble.js @@ -42,12 +42,18 @@ class AnExBobble extends React.Component { if (this.state.selectedBobble !== null) { const restSpot = BOBBLE_SPOTS[this.state.selectedBobble]; Animated.spring(this.state.bobbles[this.state.selectedBobble], { - toValue: restSpot, // return previously selected bobble to rest position + // return previously selected bobble to rest position + toValue: restSpot, + + useNativeDriver: false, }).start(); } if (newSelected !== null && newSelected !== 0) { Animated.spring(this.state.bobbles[newSelected], { - toValue: this.state.bobbles[0], // newly selected should track the selector + // newly selected should track the selector + toValue: this.state.bobbles[0], + + useNativeDriver: false, }).start(); } this.state.selectedBobble = newSelected; @@ -56,7 +62,10 @@ class AnExBobble extends React.Component { const releaseBobble = () => { this.state.bobbles.forEach((bobble, i) => { Animated.spring(bobble, { - toValue: {x: 0, y: 0}, // all bobbles return to zero + // all bobbles return to zero + toValue: {x: 0, y: 0}, + + useNativeDriver: false, }).start(); }); }; @@ -65,8 +74,13 @@ class AnExBobble extends React.Component { onPanResponderGrant: () => { BOBBLE_SPOTS.forEach((spot, idx) => { Animated.spring(this.state.bobbles[idx], { - toValue: spot, // spring each bobble to its spot - friction: 3, // less friction => bouncier + // spring each bobble to its spot + toValue: spot, + + // less friction => bouncier + friction: 3, + + useNativeDriver: false, }).start(); }); }, diff --git a/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExChained.js b/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExChained.js index 710a771f075..f8128a629ec 100644 --- a/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExChained.js +++ b/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExChained.js @@ -25,7 +25,11 @@ class AnExChained extends React.Component { const sticker = new Animated.ValueXY(); Animated.spring(sticker, { ...stickerConfig, - toValue: this.state.stickers[i], // Animated toValue's are tracked + + // Animated toValue's are tracked + toValue: this.state.stickers[i], + + useNativeDriver: false, }).start(); this.state.stickers.push(sticker); // push on the followers } @@ -36,10 +40,15 @@ class AnExChained extends React.Component { Animated.decay(this.state.stickers[0], { // coast to a stop velocity: {x: gestureState.vx, y: gestureState.vy}, + deceleration: 0.997, + useNativeDriver: false, }), Animated.spring(this.state.stickers[0], { - toValue: {x: 0, y: 0}, // return to start + // return to start + toValue: {x: 0, y: 0}, + + useNativeDriver: false, }), ]).start(); }; diff --git a/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExSet.js b/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExSet.js index e5074165c52..439383f5603 100644 --- a/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExSet.js +++ b/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExSet.js @@ -82,6 +82,8 @@ class AnExSet extends React.Component { inputRange: [0, 300], // and interpolate pixel distance outputRange: [1, 0], // to a fraction. }), + + useNativeDriver: false, }).start(); }, onPanResponderMove: Animated.event( @@ -92,7 +94,10 @@ class AnExSet extends React.Component { this.props.onDismiss(gestureState.vy); // delegates dismiss action to parent } else { Animated.spring(this.props.openVal, { - toValue: 1, // animate back open if released early + // animate back open if released early + toValue: 1, + + useNativeDriver: false, }).start(); } }, diff --git a/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExTilt.js b/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExTilt.js index 005177a8196..61ad3591883 100644 --- a/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExTilt.js +++ b/RNTester/js/examples/Animated/AnimatedGratuitousApp/AnExTilt.js @@ -29,7 +29,11 @@ class AnExTilt extends React.Component { inputRange: [-300, 0, 300], // pan is in pixels outputRange: [0, 1, 0], // goes to zero at both edges }), - duration: 0, // direct tracking + + // direct tracking + duration: 0, + + useNativeDriver: false, }).start(); }, onPanResponderMove: Animated.event( @@ -43,10 +47,15 @@ class AnExTilt extends React.Component { toValue = -500; } Animated.spring(this.state.panX, { - toValue, // animate back to center or off screen - velocity: gestureState.vx, // maintain gesture velocity + // animate back to center or off screen + toValue, + + // maintain gesture velocity + velocity: gestureState.vx, + tension: 10, friction: 3, + useNativeDriver: false, }).start(); this.state.panX.removeAllListeners(); const id = this.state.panX.addListener(({value}) => { @@ -54,7 +63,10 @@ class AnExTilt extends React.Component { if (Math.abs(value) > 400) { this.state.panX.removeListener(id); // offscreen, so stop listening Animated.timing(this.state.opacity, { - toValue: 1, // Fade back in. This unlinks it from tracking this.state.panX + // Fade back in. This unlinks it from tracking this.state.panX + toValue: 1, + + useNativeDriver: false, }).start(); this.state.panX.setValue(0); // Note: stops the spring animation toValue !== 0 && this._startBurnsZoom(); @@ -67,8 +79,13 @@ class AnExTilt extends React.Component { _startBurnsZoom() { this.state.burns.setValue(1); // reset to beginning Animated.decay(this.state.burns, { - velocity: 1, // subtle zoom - deceleration: 0.9999, // slow decay + // subtle zoom + velocity: 1, + + // slow decay + deceleration: 0.9999, + + useNativeDriver: false, }).start(); } diff --git a/RNTester/js/examples/Touchable/TouchableExample.js b/RNTester/js/examples/Touchable/TouchableExample.js index f77b7db9a54..c1767e44418 100644 --- a/RNTester/js/examples/Touchable/TouchableExample.js +++ b/RNTester/js/examples/Touchable/TouchableExample.js @@ -494,7 +494,11 @@ exports.examples = [ platform: 'android', render: function() { const mScale = new Animated.Value(1); - Animated.timing(mScale, {toValue: 0.3, duration: 1000}).start(); + Animated.timing(mScale, { + toValue: 0.3, + duration: 1000, + useNativeDriver: false, + }).start(); const style = { backgroundColor: 'rgb(180, 64, 119)', width: 200, diff --git a/RNTester/js/examples/Transform/TransformExample.js b/RNTester/js/examples/Transform/TransformExample.js index 256a4b385e2..031f7e5cbeb 100644 --- a/RNTester/js/examples/Transform/TransformExample.js +++ b/RNTester/js/examples/Transform/TransformExample.js @@ -27,6 +27,7 @@ class Flip extends React.Component<{}, $FlowFixMeState> { Animated.timing(this.state.theta, { toValue: 360, duration: 5000, + useNativeDriver: false, }).start(this._animate); };