Skip to content
Permalink
Browse files

Add support for native animations on iOS

Summary:
Currently on iOS animations are being performed on the JS thread. This ports animations over to the native thread and performs them natively. A lot of this work has already been done on Android, but this PR enables a few animation nodes that Android doesn't yet support such as Transform, Multiplication, and Addition nodes.
Also there is a demo of the native animations added to the UIExplorer app.
Closes #7884

Reviewed By: javache

Differential Revision: D3409179

Pulled By: nicklockwood

fbshipit-source-id: ef2d8840032e0c32f49e4a16ba86d448662e1751
  • Loading branch information...
Brandon Withrow Facebook Github Bot 3
Brandon Withrow authored and Facebook Github Bot 3 committed Jun 9, 2016
1 parent 9bdb63c commit 19e2388a76a7792ace166b64b9f1fc4695b62f1f
Showing with 1,985 additions and 8 deletions.
  1. +308 −0 Examples/UIExplorer/NativeAnimationsExample.js
  2. +30 −0 Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
  3. +1 −1 Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme
  4. +4 −0 Examples/UIExplorer/UIExplorerList.ios.js
  5. +33 −1 Libraries/Animated/src/AnimatedImplementation.js
  6. +17 −2 Libraries/Animated/src/NativeAnimatedHelper.js
  7. +4 −4 Libraries/Animated/src/__tests__/AnimatedNative-test.js
  8. +14 −0 Libraries/NativeAnimation/Nodes/RCTAdditionAnimatedNode.h
  9. +28 −0 Libraries/NativeAnimation/Nodes/RCTAdditionAnimatedNode.m
  10. +54 −0 Libraries/NativeAnimation/Nodes/RCTAnimatedNode.h
  11. +135 −0 Libraries/NativeAnimation/Nodes/RCTAnimatedNode.m
  12. +40 −0 Libraries/NativeAnimation/Nodes/RCTAnimationDriverNode.h
  13. +137 −0 Libraries/NativeAnimation/Nodes/RCTAnimationDriverNode.m
  14. +14 −0 Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.h
  15. +97 −0 Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.m
  16. +14 −0 Libraries/NativeAnimation/Nodes/RCTMultiplicationAnimatedNode.h
  17. +29 −0 Libraries/NativeAnimation/Nodes/RCTMultiplicationAnimatedNode.m
  18. +24 −0 Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.h
  19. +61 −0 Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m
  20. +16 −0 Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.h
  21. +59 −0 Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.m
  22. +16 −0 Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.h
  23. +52 −0 Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.m
  24. +17 −0 Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.h
  25. +14 −0 Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.m
  26. +337 −0 Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj
  27. +22 −0 Libraries/NativeAnimation/RCTAnimationUtils.h
  28. +32 −0 Libraries/NativeAnimation/RCTAnimationUtils.m
  29. +13 −0 Libraries/NativeAnimation/RCTNativeAnimatedModule.h
  30. +247 −0 Libraries/NativeAnimation/RCTNativeAnimatedModule.m
  31. +22 −0 Libraries/NativeAnimation/RCTViewPropertyMapper.h
  32. +94 −0 Libraries/NativeAnimation/RCTViewPropertyMapper.m
@@ -0,0 +1,308 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
*/
'use strict';

var React = require('react');
var ReactNative = require('react-native');
var {
View,
Text,
Animated,
StyleSheet,
TouchableWithoutFeedback,
} = ReactNative;
var UIExplorerButton = require('./UIExplorerButton');

var Tester = React.createClass({
current: 0,
getInitialState() {
return {
native: new Animated.Value(0),
js: new Animated.Value(0),
};
},

onPress() {
this.current = this.current ? 0 : 1;
const config = {
...this.props.config,
toValue: this.current,
};
try {
Animated[this.props.type](this.state.native, { ...config, useNativeDriver: true }).start();
} catch (e) {
// uncomment this if you want to get the redbox errors!
throw e;
}
Animated[this.props.type](this.state.js, { ...config, useNativeDriver: false }).start();
},

render() {
return (
<TouchableWithoutFeedback onPress={this.onPress}>
<View>
<View>
<Text>Native:</Text>
</View>
<View style={styles.row}>
{this.props.children(this.state.native)}
</View>
<View>
<Text>JavaScript:</Text>
</View>
<View style={styles.row}>
{this.props.children(this.state.js)}
</View>
</View>
</TouchableWithoutFeedback>
);
},
});

const styles = StyleSheet.create({
row: {
padding: 10,
zIndex: 1,
},
block: {
width: 50,
height: 50,
backgroundColor: 'blue',
},
});

exports.framework = 'React';
exports.title = 'Native Animated Example';
exports.description = 'Test out Native Animations';

exports.examples = [
{
title: 'Multistage With Multiply and rotation',
description: 'description',
render: function() {
return (
<Tester
type="timing"
config={{ duration: 1000 }}
>
{anim => (
<Animated.View
style={[
styles.block,
{
transform: [
{
translateX: anim.interpolate({
inputRange: [0, 1],
outputRange: [0, 200],
})
},
{
translateY: anim.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [0, 50, 0],
})
},
{
rotate: anim.interpolate({
inputRange: [0, 0.5, 1],
outputRange: ['0deg', '90deg', '0deg'],
})
}
],
opacity: Animated.multiply(
anim.interpolate({
inputRange: [0,1],
outputRange: [1,0]
}), anim.interpolate({
inputRange: [0,1],
outputRange: [0.25,1]
})
)
}
]}
/>
)}
</Tester>
);
},
},
{
title: 'Multistage With Multiply',
description: 'description',
render: function() {
return (
<Tester
type="timing"
config={{ duration: 1000 }}
>
{anim => (
<Animated.View
style={[
styles.block,
{
transform: [
{
translateX: anim.interpolate({
inputRange: [0, 1],
outputRange: [0, 200],
})
},
{
translateY: anim.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [0, 50, 0],
})
}
],
opacity: Animated.multiply(
anim.interpolate({
inputRange: [0,1],
outputRange: [1,0]
}), anim.interpolate({
inputRange: [0,1],
outputRange: [0.25,1]
})
)
}
]}
/>
)}
</Tester>
);
},
},
{
title: 'Scale interpolation',
description: 'description',
render: function() {
return (
<Tester
type="timing"
config={{ duration: 1000 }}
>
{anim => (
<Animated.View
style={[
styles.block,
{
transform: [
{
scale: anim.interpolate({
inputRange: [0, 1],
outputRange: [1, 1.4],
})
}
],
}
]}
/>
)}
</Tester>
);
},
},
{
title: 'Opacity without interpolation',
description: 'description',
render: function() {
return (
<Tester
type="timing"
config={{ duration: 1000 }}
>
{anim => (
<Animated.View
style={[
styles.block,
{
opacity: anim
}
]}
/>
)}
</Tester>
);
},
},
{
title: 'Rotate interpolation',
description: 'description',
render: function() {
return (
<Tester
type="timing"
config={{ duration: 1000 }}
>
{anim => (
<Animated.View
style={[
styles.block,
{
transform: [
{
rotate: anim.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '90deg'],
})
}
],
}
]}
/>
)}
</Tester>
);
},
},
{
title: 'translateX => Animated.spring',
description: 'description',
render: function() {
return (
<Tester
type="spring"
config={{ bounciness: 0 }}
>
{anim => (
<Animated.View
style={[
styles.block,
{
transform: [
{
translateX: anim.interpolate({
inputRange: [0, 1],
outputRange: [0, 100],
})
}
],
}
]}
/>
)}
</Tester>
);
},
},
];
@@ -32,6 +32,7 @@
13BCE84F1C9C209600DD7AAD /* RCTComponentPropsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BCE84E1C9C209600DD7AAD /* RCTComponentPropsTests.m */; };
13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DB03471B5D2ED500C27245 /* RCTJSONTests.m */; };
13DF61B61B67A45000EDB188 /* RCTMethodArgumentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */; };
13E501F11D07A84A005F35D8 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13E501A31D07A502005F35D8 /* libRCTAnimation.a */; };
143BC5A11B21E45C00462512 /* UIExplorerSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */; };
144D21241B2204C5006DB32B /* RCTImageUtilTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 144D21231B2204C5006DB32B /* RCTImageUtilTests.m */; };
147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 147CED4B1AB34F8C00DA3E4C /* libRCTActionSheet.a */; };
@@ -121,6 +122,13 @@
remoteGlobalIDString = 3C86DF461ADF2C930047B81A;
remoteInfo = RCTWebSocket;
};
13E501A21D07A502005F35D8 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 13E5019C1D07A502005F35D8 /* RCTAnimation.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RCTAnimation;
};
143BC59B1B21E3E100462512 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
@@ -210,6 +218,7 @@
13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../../Libraries/Settings/RCTSettings.xcodeproj; sourceTree = "<group>"; };
13DB03471B5D2ED500C27245 /* RCTJSONTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJSONTests.m; sourceTree = "<group>"; };
13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMethodArgumentTests.m; sourceTree = "<group>"; };
13E5019C1D07A502005F35D8 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = ../../Libraries/NativeAnimation/RCTAnimation.xcodeproj; sourceTree = "<group>"; };
143BC57E1B21E18100462512 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
143BC5811B21E18100462512 /* testLayoutExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testLayoutExampleSnapshot_1@2x.png"; sourceTree = "<group>"; };
143BC5821B21E18100462512 /* testSliderExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testSliderExampleSnapshot_1@2x.png"; sourceTree = "<group>"; };
@@ -285,6 +294,7 @@
14AADF051AC3DBB1002390C9 /* libReact.a in Frameworks */,
147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */,
134454601AAFCABD003F0779 /* libRCTAdSupport.a in Frameworks */,
13E501F11D07A84A005F35D8 /* libRCTAnimation.a in Frameworks */,
138DEE241B9EDFB6007F4EA5 /* libRCTCameraRoll.a in Frameworks */,
134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */,
13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */,
@@ -315,6 +325,7 @@
14AADEFF1AC3DB95002390C9 /* React.xcodeproj */,
14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */,
134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */,
13E5019C1D07A502005F35D8 /* RCTAnimation.xcodeproj */,
138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */,
134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */,
13417FE31AA91428003F314A /* RCTImage.xcodeproj */,
@@ -413,6 +424,14 @@
name = UIExplorer;
sourceTree = "<group>";
};
13E5019D1D07A502005F35D8 /* Products */ = {
isa = PBXGroup;
children = (
13E501A31D07A502005F35D8 /* libRCTAnimation.a */,
);
name = Products;
sourceTree = "<group>";
};
143BC57C1B21E18100462512 /* UIExplorerUnitTests */ = {
isa = PBXGroup;
children = (
@@ -693,6 +712,10 @@
ProductGroup = 134454561AAFCAAE003F0779 /* Products */;
ProjectRef = 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */;
},
{
ProductGroup = 13E5019D1D07A502005F35D8 /* Products */;
ProjectRef = 13E5019C1D07A502005F35D8 /* RCTAnimation.xcodeproj */;
},
{
ProductGroup = 138DEE031B9EDDDB007F4EA5 /* Products */;
ProjectRef = 138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */;
@@ -801,6 +824,13 @@
remoteRef = 139FDED81B0651EA00C62182 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
13E501A31D07A502005F35D8 /* libRCTAnimation.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTAnimation.a;
remoteRef = 13E501A21D07A502005F35D8 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
147CED4B1AB34F8C00DA3E4C /* libRCTActionSheet.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
Oops, something went wrong.

2 comments on commit 19e2388

@aleclarson

This comment has been minimized.

Copy link
Contributor

aleclarson replied Jul 7, 2016

Hey @buba447, thanks for all your work on this! 😄

@rclai

This comment has been minimized.

Copy link
Contributor

rclai replied Jul 11, 2016

Hey guys, thanks for all this work. I was wondering, is it possible to use the native driver when you're driving an Animated.Value manually without using the timing functions or is this not supported yet? For example, syncing a ScrollView's offset using this.myAnimatedValue.setValue(scrollY).

Please sign in to comment.
You can’t perform that action at this time.