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 https://github.com/facebook/react-native/pull/7884

Reviewed By: javache

Differential Revision: D3409179

Pulled By: nicklockwood

fbshipit-source-id: ef2d8840032e0c32f49e4a16ba86d448662e1751
This commit is contained in:
Brandon Withrow 2016-06-09 10:34:41 -07:00 committed by Facebook Github Bot 3
parent 9bdb63c234
commit 19e2388a76
32 changed files with 1985 additions and 8 deletions

View File

@ -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>
);
},
},
];

View File

@ -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;

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -68,6 +68,10 @@ const ComponentExamples: Array<UIExplorerExample> = [
key: 'ModalExample',
module: require('./ModalExample'),
},
{
key: 'NativeAnimationsExample',
module: require('./NativeAnimationsExample'),
},
{
key: 'NavigatorExample',
module: require('./Navigator/NavigatorExample'),

View File

@ -243,7 +243,7 @@ class TimingAnimation extends Animation {
this._duration = config.duration !== undefined ? config.duration : 500;
this._delay = config.delay !== undefined ? config.delay : 0;
this.__isInteraction = config.isInteraction !== undefined ? config.isInteraction : true;
this._useNativeDriver = !!config.useNativeDriver;
this._useNativeDriver = config.useNativeDriver !== undefined ? config.useNativeDriver : false;
}
_getNativeAnimationConfig(): any {
@ -256,6 +256,7 @@ class TimingAnimation extends Animation {
type: 'frames',
frames,
toValue: this._toValue,
delay: this._delay
};
}
@ -1086,6 +1087,18 @@ class AnimatedTransform extends AnimatedWithChildren {
this._transforms = transforms;
}
__makeNative() {
super.__makeNative();
this._transforms.forEach(transform => {
for (var key in transform) {
var value = transform[key];
if (value instanceof Animated) {
value.__makeNative();
}
}
});
}
__getValue(): Array<Object> {
return this._transforms.map(transform => {
var result = {};
@ -1138,6 +1151,25 @@ class AnimatedTransform extends AnimatedWithChildren {
}
});
}
__getNativeConfig(): any {
var transConfig = {};
this._transforms.forEach(transform => {
for (var key in transform) {
var value = transform[key];
if (value instanceof Animated) {
transConfig[key] = value.__getNativeTag();
}
}
});
NativeAnimatedHelper.validateTransform(transConfig);
return {
type: 'transform',
transform: transConfig,
};
}
}
class AnimatedStyle extends AnimatedWithChildren {

View File

@ -74,16 +74,22 @@ var API = {
var PROPS_WHITELIST = {
style: {
opacity: true,
transform: true,
/* legacy android transform properties */
scaleX: true,
scaleY: true,
rotation: true,
translateX: true,
translateY: true,
},
};
var TRANSFORM_WHITELIST = {
translateX: true,
translateY: true,
scale: true,
rotate: true,
};
function validateProps(params: Object): void {
for (var key in params) {
if (!PROPS_WHITELIST.hasOwnProperty(key)) {
@ -92,6 +98,14 @@ function validateProps(params: Object): void {
}
}
function validateTransform(config: Object): void {
for (var key in config) {
if (!TRANSFORM_WHITELIST.hasOwnProperty(key)) {
throw new Error(`Property '${key}' is not supported by native animated module`);
}
}
}
function validateStyles(styles: Object): void {
var STYLES_WHITELIST = PROPS_WHITELIST.style || {};
for (var key in styles) {
@ -129,6 +143,7 @@ module.exports = {
API,
validateProps,
validateStyles,
validateTransform,
validateInterpolation,
generateNewNodeTag,
generateNewAnimationId,

View File

@ -62,7 +62,7 @@ describe('Animated', () => {
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
jasmine.any(Number),
jasmine.any(Number),
{type: 'frames', frames: jasmine.any(Array), toValue: jasmine.any(Number)},
{type: 'frames', frames: jasmine.any(Array), toValue: jasmine.any(Number), delay: jasmine.any(Number)},
jasmine.any(Function)
);
@ -201,7 +201,7 @@ describe('Animated', () => {
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
jasmine.any(Number),
jasmine.any(Number),
{type: 'frames', frames: jasmine.any(Array), toValue: jasmine.any(Number)},
{type: 'frames', frames: jasmine.any(Array), toValue: jasmine.any(Number), delay: jasmine.any(Number)},
jasmine.any(Function)
);
});
@ -341,7 +341,7 @@ describe('Animated', () => {
.toBeCalledWith(jasmine.any(Number), { type: 'props', props: { style: jasmine.any(Number) }});
});
it('send stopAnimation command to native', () => {
it('sends stopAnimation command to native', () => {
var value = new Animated.Value(0);
var animation = Animated.timing(value, {toValue: 10, duration: 50, useNativeDriver: true});
var nativeAnimatedModule = require('NativeModules').NativeAnimatedModule;
@ -350,7 +350,7 @@ describe('Animated', () => {
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
jasmine.any(Number),
jasmine.any(Number),
{type: 'frames', frames: jasmine.any(Array), toValue: jasmine.any(Number)},
{type: 'frames', frames: jasmine.any(Array), toValue: jasmine.any(Number), delay: jasmine.any(Number)},
jasmine.any(Function)
);
var animationId = nativeAnimatedModule.startAnimatingNode.mock.calls[0][0];

View File

@ -0,0 +1,14 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTValueAnimatedNode.h"
@interface RCTAdditionAnimatedNode : RCTValueAnimatedNode
@end

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTAdditionAnimatedNode.h"
@implementation RCTAdditionAnimatedNode
- (void)performUpdate
{
[super performUpdate];
NSArray<NSNumber *> *inputNodes = self.config[@"input"];
if (inputNodes.count > 1) {
RCTValueAnimatedNode *parent1 = (RCTValueAnimatedNode *)self.parentNodes[inputNodes[0]];
RCTValueAnimatedNode *parent2 = (RCTValueAnimatedNode *)self.parentNodes[inputNodes[1]];
if ([parent1 isKindOfClass:[RCTValueAnimatedNode class]] &&
[parent2 isKindOfClass:[RCTValueAnimatedNode class]]) {
self.value = parent1.value + parent2.value;
}
}
}
@end

View File

@ -0,0 +1,54 @@
/**
* Copyright (c) 2015-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.
*/
#import <Foundation/Foundation.h>
@interface RCTAnimatedNode : NSObject
- (instancetype)initWithTag:(NSNumber *)tag
config:(NSDictionary<NSString *, id> *)config NS_DESIGNATED_INITIALIZER;
@property (nonatomic, readonly) NSNumber *nodeTag;
@property (nonatomic, copy, readonly) NSDictionary<NSString *, id> *config;
@property (nonatomic, copy, readonly) NSDictionary<NSNumber *, RCTAnimatedNode *> *childNodes;
@property (nonatomic, copy, readonly) NSDictionary<NSNumber *, RCTAnimatedNode *> *parentNodes;
@property (nonatomic, readonly) BOOL needsUpdate;
@property (nonatomic, readonly) BOOL hasUpdated;
/**
* Marks a node and its children as needing update.
*/
- (void)setNeedsUpdate NS_REQUIRES_SUPER;
/**
* The node will update its value if necesarry and only after its parents have updated.
*/
- (void)updateNodeIfNecessary NS_REQUIRES_SUPER;
/**
* Where the actual update code lives. Called internally from updateNodeIfNecessary
*/
- (void)performUpdate NS_REQUIRES_SUPER;
/**
* Cleans up after a round of updates.
*/
- (void)cleanupAnimationUpdate NS_REQUIRES_SUPER;
- (void)addChild:(RCTAnimatedNode *)child NS_REQUIRES_SUPER;
- (void)removeChild:(RCTAnimatedNode *)child NS_REQUIRES_SUPER;
- (void)onAttachedToNode:(RCTAnimatedNode *)parent NS_REQUIRES_SUPER;
- (void)onDetachedFromNode:(RCTAnimatedNode *)parent NS_REQUIRES_SUPER;
- (void)detachNode NS_REQUIRES_SUPER;
@end

View File

@ -0,0 +1,135 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTAnimatedNode.h"
#import "RCTDefines.h"
@implementation RCTAnimatedNode
{
NSMutableDictionary<NSNumber *, RCTAnimatedNode *> *_childNodes;
NSMutableDictionary<NSNumber *, RCTAnimatedNode *> *_parentNodes;
}
- (instancetype)initWithTag:(NSNumber *)tag
config:(NSDictionary<NSString *, id> *)config
{
if ((self = [super init])) {
_nodeTag = tag;
_config = [config copy];
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (NSDictionary<NSNumber *, RCTAnimatedNode *> *)childNodes
{
return _childNodes;
}
- (NSDictionary<NSNumber *, RCTAnimatedNode *> *)parentNodes
{
return _parentNodes;
}
- (void)addChild:(RCTAnimatedNode *)child
{
if (!_childNodes) {
_childNodes = [NSMutableDictionary new];
}
if (child) {
_childNodes[child.nodeTag] = child;
[child onAttachedToNode:self];
}
}
- (void)removeChild:(RCTAnimatedNode *)child
{
if (!_childNodes) {
return;
}
if (child) {
[_childNodes removeObjectForKey:child.nodeTag];
[child onDetachedFromNode:self];
}
}
- (void)onAttachedToNode:(RCTAnimatedNode *)parent
{
if (!_parentNodes) {
_parentNodes = [NSMutableDictionary new];
}
if (parent) {
_parentNodes[parent.nodeTag] = parent;
}
}
- (void)onDetachedFromNode:(RCTAnimatedNode *)parent
{
if (!_parentNodes) {
return;
}
if (parent) {
[_parentNodes removeObjectForKey:parent.nodeTag];
}
}
- (void)detachNode
{
for (RCTAnimatedNode *parent in _parentNodes.allValues) {
[parent removeChild:self];
}
for (RCTAnimatedNode *child in _childNodes.allValues) {
[self removeChild:child];
}
}
- (void)setNeedsUpdate
{
if (_needsUpdate) {
// Has already been marked. Stop branch.
return;
}
_needsUpdate = YES;
for (RCTAnimatedNode *child in _childNodes.allValues) {
[child setNeedsUpdate];
}
}
- (void)cleanupAnimationUpdate
{
if (_hasUpdated) {
_needsUpdate = NO;
_hasUpdated = NO;
for (RCTAnimatedNode *child in _childNodes.allValues) {
[child cleanupAnimationUpdate];
}
}
}
- (void)updateNodeIfNecessary
{
if (_needsUpdate && !_hasUpdated) {
for (RCTAnimatedNode *parent in _parentNodes.allValues) {
[parent updateNodeIfNecessary];
}
[self performUpdate];
}
}
- (void)performUpdate
{
_hasUpdated = YES;
// To be overidden by subclasses
// This method is called on a node only if it has been marked for update
// during the current update loop
}
@end

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) 2015-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.
*/
#import <CoreGraphics/CoreGraphics.h>
#import "RCTBridgeModule.h"
@class RCTValueAnimatedNode;
NS_ASSUME_NONNULL_BEGIN
@interface RCTAnimationDriverNode : NSObject
@property (nonatomic, readonly) NSNumber *animationId;
@property (nonatomic, readonly) NSNumber *outputValue;
@property (nonatomic, readonly) BOOL animationHasBegun;
@property (nonatomic, readonly) BOOL animationHasFinished;
- (instancetype)initWithId:(NSNumber *)animationId
delay:(NSTimeInterval)delay
toValue:(CGFloat)toValue
frames:(NSArray<NSNumber *> *)frames
forNode:(RCTValueAnimatedNode *)valueNode
callBack:(nullable RCTResponseSenderBlock)callback NS_DESIGNATED_INITIALIZER;
- (void)startAnimation;
- (void)stopAnimation;
- (void)stepAnimation;
- (void)removeAnimation;
- (void)cleanupAnimationUpdate;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,137 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTAnimationDriverNode.h"
#import <UIKit/UIKit.h>
#import "RCTAnimationUtils.h"
#import "RCTDefines.h"
#import "RCTValueAnimatedNode.h"
const double SINGLE_FRAME_INTERVAL = 1.0 / 60.0;
@implementation RCTAnimationDriverNode
{
NSArray<NSNumber *> *_frames;
CGFloat _toValue;
CGFloat _fromValue;
NSTimeInterval _delay;
NSTimeInterval _animationStartTime;
NSTimeInterval _animationCurrentTime;
RCTValueAnimatedNode *_valueNode;
RCTResponseSenderBlock _callback;
}
- (instancetype)initWithId:(nonnull NSNumber *)animationId
delay:(NSTimeInterval)delay
toValue:(CGFloat)toValue
frames:(nonnull NSArray<NSNumber *> *)frames
forNode:(nonnull RCTValueAnimatedNode *)valueNode
callBack:(nullable RCTResponseSenderBlock)callback
{
if ((self = [super init])) {
_animationId = animationId;
_toValue = toValue;
_fromValue = valueNode.value;
_valueNode = valueNode;
_delay = delay;
_frames = [frames copy];
_outputValue = @0;
_callback = [callback copy];
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)startAnimation
{
_animationStartTime = CACurrentMediaTime();
_animationCurrentTime = _animationStartTime;
_animationHasBegun = YES;
}
- (void)stopAnimation
{
_animationHasFinished = YES;
}
- (void)removeAnimation
{
[self stopAnimation];
_valueNode = nil;
if (_callback) {
_callback(@[(id)kCFNull]);
}
}
- (void)stepAnimation
{
if (!_animationHasBegun || _animationHasFinished || _frames.count == 0) {
// Animation has not begun or animation has already finished.
return;
}
NSTimeInterval currentTime = CACurrentMediaTime();
NSTimeInterval stepInterval = currentTime - _animationCurrentTime;
_animationCurrentTime = currentTime;
NSTimeInterval currentDuration = _animationCurrentTime - _animationStartTime;
if (_delay > 0) {
// Decrement delay
_delay -= stepInterval;
return;
}
// Determine how many frames have passed since last update.
// Get index of frames that surround the current interval
NSUInteger startIndex = floor(currentDuration / SINGLE_FRAME_INTERVAL);
NSUInteger nextIndex = startIndex + 1;
if (nextIndex >= _frames.count) {
// We are at the end of the animation
// Update value and flag animation has ended.
NSNumber *finalValue = _frames.lastObject;
[self updateOutputWithFrameOutput:finalValue.doubleValue];
[self stopAnimation];
return;
}
// Do a linear remap of the two frames to safegaurd against variable framerates
NSNumber *fromFrameValue = _frames[startIndex];
NSNumber *toFrameValue = _frames[nextIndex];
NSTimeInterval fromInterval = startIndex * SINGLE_FRAME_INTERVAL;
NSTimeInterval toInterval = nextIndex * SINGLE_FRAME_INTERVAL;
// Interpolate between the individual frames to ensure the animations are
//smooth and of the proper duration regardless of the framerate.
CGFloat frameOutput = RCTInterpolateValue(currentDuration,
fromInterval,
toInterval,
fromFrameValue.doubleValue,
toFrameValue.doubleValue);
[self updateOutputWithFrameOutput:frameOutput];
}
- (void)updateOutputWithFrameOutput:(CGFloat)frameOutput
{
CGFloat outputValue = RCTInterpolateValue(frameOutput, 0, 1, _fromValue, _toValue);
_outputValue = @(outputValue);
_valueNode.value = outputValue;
[_valueNode setNeedsUpdate];
}
- (void)cleanupAnimationUpdate
{
[_valueNode cleanupAnimationUpdate];
}
@end

View File

@ -0,0 +1,14 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTValueAnimatedNode.h"
@interface RCTInterpolationAnimatedNode : RCTValueAnimatedNode
@end

View File

@ -0,0 +1,97 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTInterpolationAnimatedNode.h"
#import "RCTAnimationUtils.h"
@implementation RCTInterpolationAnimatedNode
{
__weak RCTValueAnimatedNode *_parentNode;
NSArray<NSNumber *> *_inputRange;
NSArray<NSNumber *> *_outputRange;
}
- (instancetype)initWithTag:(NSNumber *)tag
config:(NSDictionary<NSString *, id> *)config
{
if ((self = [super initWithTag:tag config:config])) {
_inputRange = [config[@"inputRange"] copy];
NSMutableArray *outputRange = [NSMutableArray array];
for (id value in config[@"outputRange"]) {
if ([value isKindOfClass:[NSNumber class]]) {
[outputRange addObject:value];
} else if ([value isKindOfClass:[NSString class]]) {
NSString *str = (NSString *)value;
if ([str hasSuffix:@"deg"]) {
double degrees = str.doubleValue;
[outputRange addObject:@(RCTDegreesToRadians(degrees))];
} else {
// Assume radians
[outputRange addObject:@(str.doubleValue)];
}
}
}
_outputRange = [outputRange copy];
}
return self;
}
- (void)onAttachedToNode:(RCTAnimatedNode *)parent
{
[super onAttachedToNode:parent];
if ([parent isKindOfClass:[RCTValueAnimatedNode class]]) {
_parentNode = (RCTValueAnimatedNode *)parent;
}
}
- (void)onDetachedFromNode:(RCTAnimatedNode *)parent
{
[super onDetachedFromNode:parent];
if (_parentNode == parent) {
_parentNode = nil;
}
}
- (NSUInteger)findIndexOfNearestValue:(CGFloat)value
inRange:(NSArray<NSNumber *> *)range
{
NSUInteger index;
NSUInteger rangeCount = range.count;
for (index = 1; index < rangeCount - 1; index++) {
NSNumber *inputValue = range[index];
if (inputValue.doubleValue >= value) {
break;
}
}
return index - 1;
}
- (void)performUpdate
{
[super performUpdate];
if (!_parentNode) {
return;
}
NSUInteger rangeIndex = [self findIndexOfNearestValue:_parentNode.value
inRange:_inputRange];
NSNumber *inputMin = _inputRange[rangeIndex];
NSNumber *inputMax = _inputRange[rangeIndex + 1];
NSNumber *outputMin = _outputRange[rangeIndex];
NSNumber *outputMax = _outputRange[rangeIndex + 1];
CGFloat outputValue = RCTInterpolateValue(_parentNode.value,
inputMin.doubleValue,
inputMax.doubleValue,
outputMin.doubleValue,
outputMax.doubleValue);
self.value = outputValue;
}
@end

View File

@ -0,0 +1,14 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTValueAnimatedNode.h"
@interface RCTMultiplicationAnimatedNode : RCTValueAnimatedNode
@end

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTMultiplicationAnimatedNode.h"
@implementation RCTMultiplicationAnimatedNode
- (void)performUpdate
{
[super performUpdate];
NSArray<NSNumber *> *inputNodes = self.config[@"input"];
if (inputNodes.count > 1) {
RCTValueAnimatedNode *parent1 = (RCTValueAnimatedNode *)self.parentNodes[inputNodes[0]];
RCTValueAnimatedNode *parent2 = (RCTValueAnimatedNode *)self.parentNodes[inputNodes[1]];
if ([parent1 isKindOfClass:[RCTValueAnimatedNode class]] &&
[parent2 isKindOfClass:[RCTValueAnimatedNode class]]) {
self.value = parent1.value * parent2.value;
}
}
}
@end

View File

@ -0,0 +1,24 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTAnimatedNode.h"
@class RCTNativeAnimatedModule;
@class RCTViewPropertyMapper;
@interface RCTPropsAnimatedNode : RCTAnimatedNode
@property (nonatomic, readonly) RCTViewPropertyMapper *propertyMapper;
- (void)connectToView:(NSNumber *)viewTag animatedModule:(RCTNativeAnimatedModule *)animationModule;
- (void)disconnectFromView:(NSNumber *)viewTag;
- (void)performViewUpdatesIfNecessary;
@end

View File

@ -0,0 +1,61 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTPropsAnimatedNode.h"
#import "RCTAnimationUtils.h"
#import "RCTNativeAnimatedModule.h"
#import "RCTStyleAnimatedNode.h"
#import "RCTViewPropertyMapper.h"
@implementation RCTPropsAnimatedNode
{
RCTStyleAnimatedNode *_parentNode;
}
- (void)onAttachedToNode:(RCTAnimatedNode *)parent
{
[super onAttachedToNode:parent];
if ([parent isKindOfClass:[RCTStyleAnimatedNode class]]) {
_parentNode = (RCTStyleAnimatedNode *)parent;
}
}
- (void)onDetachedFromNode:(RCTAnimatedNode *)parent
{
[super onDetachedFromNode:parent];
if (_parentNode == parent) {
_parentNode = nil;
}
}
- (void)connectToView:(NSNumber *)viewTag animatedModule:(RCTNativeAnimatedModule *)animationModule
{
_propertyMapper = [[RCTViewPropertyMapper alloc] initWithViewTag:viewTag animationModule:animationModule];
}
- (void)disconnectFromView:(NSNumber *)viewTag
{
_propertyMapper = nil;
}
- (void)performUpdate
{
[super performUpdate];
[self performViewUpdatesIfNecessary];
}
- (void)performViewUpdatesIfNecessary
{
NSDictionary *updates = [_parentNode updatedPropsDictionary];
if (updates.count) {
[_propertyMapper updateViewWithDictionary:updates];
}
}
@end

View File

@ -0,0 +1,16 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTAnimatedNode.h"
@interface RCTStyleAnimatedNode : RCTAnimatedNode
- (NSDictionary<NSString *, NSNumber *> *)updatedPropsDictionary;
@end

View File

@ -0,0 +1,59 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTStyleAnimatedNode.h"
#import "RCTAnimationUtils.h"
#import "RCTValueAnimatedNode.h"
#import "RCTTransformAnimatedNode.h"
@implementation RCTStyleAnimatedNode
{
NSMutableDictionary<NSString *, NSNumber *> *_updatedPropsDictionary;
}
- (instancetype)initWithTag:(NSNumber *)tag
config:(NSDictionary<NSString *, id> *)config;
{
if ((self = [super initWithTag:tag config:config])) {
_updatedPropsDictionary = [NSMutableDictionary new];
}
return self;
}
- (NSDictionary *)updatedPropsDictionary
{
return _updatedPropsDictionary;
}
- (void)performUpdate
{
[super performUpdate];
NSDictionary<NSString *, NSNumber *> *style = self.config[@"style"];
[style enumerateKeysAndObjectsUsingBlock:^(NSString *property, NSNumber *nodeTag, __unused BOOL *stop) {
RCTAnimatedNode *node = self.parentNodes[nodeTag];
if (node && node.hasUpdated) {
if ([node isKindOfClass:[RCTValueAnimatedNode class]]) {
RCTValueAnimatedNode *parentNode = (RCTValueAnimatedNode *)node;
[self->_updatedPropsDictionary setObject:@(parentNode.value) forKey:property];
} else if ([node isKindOfClass:[RCTTransformAnimatedNode class]]) {
RCTTransformAnimatedNode *parentNode = (RCTTransformAnimatedNode *)node;
[self->_updatedPropsDictionary addEntriesFromDictionary:parentNode.updatedPropsDictionary];
}
}
}];
}
- (void)cleanupAnimationUpdate
{
[super cleanupAnimationUpdate];
[_updatedPropsDictionary removeAllObjects];
}
@end

View File

@ -0,0 +1,16 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTAnimatedNode.h"
@interface RCTTransformAnimatedNode : RCTAnimatedNode
- (NSDictionary<NSString *, NSNumber *> *)updatedPropsDictionary;
@end

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTTransformAnimatedNode.h"
#import "RCTValueAnimatedNode.h"
@implementation RCTTransformAnimatedNode
{
NSMutableDictionary<NSString *, NSNumber *> *_updatedPropsDictionary;
}
- (instancetype)initWithTag:(NSNumber *)tag
config:(NSDictionary<NSString *, id> *)config;
{
if ((self = [super initWithTag:tag config:config])) {
_updatedPropsDictionary = [NSMutableDictionary new];
}
return self;
}
- (NSDictionary *)updatedPropsDictionary
{
return _updatedPropsDictionary;
}
- (void)performUpdate
{
[super performUpdate];
NSDictionary<NSString *, NSNumber *> *transforms = self.config[@"transform"];
[transforms enumerateKeysAndObjectsUsingBlock:^(NSString *property, NSNumber *nodeTag, __unused BOOL *stop) {
RCTAnimatedNode *node = self.parentNodes[nodeTag];
if (node.hasUpdated && [node isKindOfClass:[RCTValueAnimatedNode class]]) {
RCTValueAnimatedNode *parentNode = (RCTValueAnimatedNode *)node;
self->_updatedPropsDictionary[property] = @(parentNode.value);
}
}];
}
- (void)cleanupAnimationUpdate
{
[super cleanupAnimationUpdate];
[_updatedPropsDictionary removeAllObjects];
}
@end

View File

@ -0,0 +1,17 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTAnimatedNode.h"
#import <UIKit/UIKit.h>
@interface RCTValueAnimatedNode : RCTAnimatedNode
@property (nonatomic, assign) CGFloat value;
@end

View File

@ -0,0 +1,14 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTValueAnimatedNode.h"
@implementation RCTValueAnimatedNode
@end

View File

@ -0,0 +1,337 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
13E501CC1D07A644005F35D8 /* RCTAnimationUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501B81D07A644005F35D8 /* RCTAnimationUtils.m */; };
13E501CF1D07A644005F35D8 /* RCTNativeAnimatedModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501BE1D07A644005F35D8 /* RCTNativeAnimatedModule.m */; };
13E501D41D07A644005F35D8 /* RCTViewPropertyMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501C81D07A644005F35D8 /* RCTViewPropertyMapper.m */; };
13E501E81D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501D71D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m */; };
13E501E91D07A6C9005F35D8 /* RCTAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501D91D07A6C9005F35D8 /* RCTAnimatedNode.m */; };
13E501EA1D07A6C9005F35D8 /* RCTAnimationDriverNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501DB1D07A6C9005F35D8 /* RCTAnimationDriverNode.m */; };
13E501EB1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501DD1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.m */; };
13E501EC1D07A6C9005F35D8 /* RCTMultiplicationAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501DF1D07A6C9005F35D8 /* RCTMultiplicationAnimatedNode.m */; };
13E501ED1D07A6C9005F35D8 /* RCTPropsAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E11D07A6C9005F35D8 /* RCTPropsAnimatedNode.m */; };
13E501EE1D07A6C9005F35D8 /* RCTStyleAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E31D07A6C9005F35D8 /* RCTStyleAnimatedNode.m */; };
13E501EF1D07A6C9005F35D8 /* RCTTransformAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E51D07A6C9005F35D8 /* RCTTransformAnimatedNode.m */; };
13E501F01D07A6C9005F35D8 /* RCTValueAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E71D07A6C9005F35D8 /* RCTValueAnimatedNode.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
58B511D91A9E6C8500147676 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
134814201AA4EA6300B7C361 /* libRCTAnimation.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTAnimation.a; sourceTree = BUILT_PRODUCTS_DIR; };
13E501B71D07A644005F35D8 /* RCTAnimationUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimationUtils.h; sourceTree = "<group>"; };
13E501B81D07A644005F35D8 /* RCTAnimationUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAnimationUtils.m; sourceTree = "<group>"; };
13E501BD1D07A644005F35D8 /* RCTNativeAnimatedModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNativeAnimatedModule.h; sourceTree = "<group>"; };
13E501BE1D07A644005F35D8 /* RCTNativeAnimatedModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNativeAnimatedModule.m; sourceTree = "<group>"; };
13E501C71D07A644005F35D8 /* RCTViewPropertyMapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTViewPropertyMapper.h; sourceTree = "<group>"; };
13E501C81D07A644005F35D8 /* RCTViewPropertyMapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTViewPropertyMapper.m; sourceTree = "<group>"; };
13E501D61D07A6C9005F35D8 /* RCTAdditionAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAdditionAnimatedNode.h; sourceTree = "<group>"; };
13E501D71D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAdditionAnimatedNode.m; sourceTree = "<group>"; };
13E501D81D07A6C9005F35D8 /* RCTAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimatedNode.h; sourceTree = "<group>"; };
13E501D91D07A6C9005F35D8 /* RCTAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAnimatedNode.m; sourceTree = "<group>"; };
13E501DA1D07A6C9005F35D8 /* RCTAnimationDriverNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimationDriverNode.h; sourceTree = "<group>"; };
13E501DB1D07A6C9005F35D8 /* RCTAnimationDriverNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAnimationDriverNode.m; sourceTree = "<group>"; };
13E501DC1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTInterpolationAnimatedNode.h; sourceTree = "<group>"; };
13E501DD1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTInterpolationAnimatedNode.m; sourceTree = "<group>"; };
13E501DE1D07A6C9005F35D8 /* RCTMultiplicationAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMultiplicationAnimatedNode.h; sourceTree = "<group>"; };
13E501DF1D07A6C9005F35D8 /* RCTMultiplicationAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMultiplicationAnimatedNode.m; sourceTree = "<group>"; };
13E501E01D07A6C9005F35D8 /* RCTPropsAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPropsAnimatedNode.h; sourceTree = "<group>"; };
13E501E11D07A6C9005F35D8 /* RCTPropsAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPropsAnimatedNode.m; sourceTree = "<group>"; };
13E501E21D07A6C9005F35D8 /* RCTStyleAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStyleAnimatedNode.h; sourceTree = "<group>"; };
13E501E31D07A6C9005F35D8 /* RCTStyleAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStyleAnimatedNode.m; sourceTree = "<group>"; };
13E501E41D07A6C9005F35D8 /* RCTTransformAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTransformAnimatedNode.h; sourceTree = "<group>"; };
13E501E51D07A6C9005F35D8 /* RCTTransformAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTransformAnimatedNode.m; sourceTree = "<group>"; };
13E501E61D07A6C9005F35D8 /* RCTValueAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTValueAnimatedNode.h; sourceTree = "<group>"; };
13E501E71D07A6C9005F35D8 /* RCTValueAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTValueAnimatedNode.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
58B511D81A9E6C8500147676 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
134814211AA4EA7D00B7C361 /* Products */ = {
isa = PBXGroup;
children = (
134814201AA4EA6300B7C361 /* libRCTAnimation.a */,
);
name = Products;
sourceTree = "<group>";
};
13E501D51D07A6C9005F35D8 /* Nodes */ = {
isa = PBXGroup;
children = (
13E501D61D07A6C9005F35D8 /* RCTAdditionAnimatedNode.h */,
13E501D71D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m */,
13E501D81D07A6C9005F35D8 /* RCTAnimatedNode.h */,
13E501D91D07A6C9005F35D8 /* RCTAnimatedNode.m */,
13E501DA1D07A6C9005F35D8 /* RCTAnimationDriverNode.h */,
13E501DB1D07A6C9005F35D8 /* RCTAnimationDriverNode.m */,
13E501DC1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.h */,
13E501DD1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.m */,
13E501DE1D07A6C9005F35D8 /* RCTMultiplicationAnimatedNode.h */,
13E501DF1D07A6C9005F35D8 /* RCTMultiplicationAnimatedNode.m */,
13E501E01D07A6C9005F35D8 /* RCTPropsAnimatedNode.h */,
13E501E11D07A6C9005F35D8 /* RCTPropsAnimatedNode.m */,
13E501E21D07A6C9005F35D8 /* RCTStyleAnimatedNode.h */,
13E501E31D07A6C9005F35D8 /* RCTStyleAnimatedNode.m */,
13E501E41D07A6C9005F35D8 /* RCTTransformAnimatedNode.h */,
13E501E51D07A6C9005F35D8 /* RCTTransformAnimatedNode.m */,
13E501E61D07A6C9005F35D8 /* RCTValueAnimatedNode.h */,
13E501E71D07A6C9005F35D8 /* RCTValueAnimatedNode.m */,
);
path = Nodes;
sourceTree = "<group>";
};
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
13E501B71D07A644005F35D8 /* RCTAnimationUtils.h */,
13E501B81D07A644005F35D8 /* RCTAnimationUtils.m */,
13E501C71D07A644005F35D8 /* RCTViewPropertyMapper.h */,
13E501C81D07A644005F35D8 /* RCTViewPropertyMapper.m */,
13E501BD1D07A644005F35D8 /* RCTNativeAnimatedModule.h */,
13E501BE1D07A644005F35D8 /* RCTNativeAnimatedModule.m */,
13E501D51D07A6C9005F35D8 /* Nodes */,
134814211AA4EA7D00B7C361 /* Products */,
);
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
58B511DA1A9E6C8500147676 /* RCTAnimation */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTAnimation" */;
buildPhases = (
58B511D71A9E6C8500147676 /* Sources */,
58B511D81A9E6C8500147676 /* Frameworks */,
58B511D91A9E6C8500147676 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = RCTAnimation;
productName = RCTDataManager;
productReference = 134814201AA4EA6300B7C361 /* libRCTAnimation.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
58B511D31A9E6C8500147676 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0730;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
58B511DA1A9E6C8500147676 = {
CreatedOnToolsVersion = 6.1.1;
};
};
};
buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTAnimation" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 58B511D21A9E6C8500147676;
productRefGroup = 58B511D21A9E6C8500147676;
projectDirPath = "";
projectRoot = "";
targets = (
58B511DA1A9E6C8500147676 /* RCTAnimation */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
58B511D71A9E6C8500147676 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
13E501F01D07A6C9005F35D8 /* RCTValueAnimatedNode.m in Sources */,
13E501EE1D07A6C9005F35D8 /* RCTStyleAnimatedNode.m in Sources */,
13E501CC1D07A644005F35D8 /* RCTAnimationUtils.m in Sources */,
13E501CF1D07A644005F35D8 /* RCTNativeAnimatedModule.m in Sources */,
13E501EC1D07A6C9005F35D8 /* RCTMultiplicationAnimatedNode.m in Sources */,
13E501ED1D07A6C9005F35D8 /* RCTPropsAnimatedNode.m in Sources */,
13E501E91D07A6C9005F35D8 /* RCTAnimatedNode.m in Sources */,
13E501EB1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.m in Sources */,
13E501E81D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m in Sources */,
13E501EA1D07A6C9005F35D8 /* RCTAnimationDriverNode.m in Sources */,
13E501EF1D07A6C9005F35D8 /* RCTTransformAnimatedNode.m in Sources */,
13E501D41D07A644005F35D8 /* RCTViewPropertyMapper.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
58B511ED1A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_SHADOW = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
WARNING_CFLAGS = (
"-Werror",
"-Wall",
);
};
name = Debug;
};
58B511EE1A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_SHADOW = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
WARNING_CFLAGS = (
"-Werror",
"-Wall",
);
};
name = Release;
};
58B511F01A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RCTAnimation;
SKIP_INSTALL = YES;
};
name = Debug;
};
58B511F11A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RCTAnimation;
SKIP_INSTALL = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTAnimation" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511ED1A9E6C8500147676 /* Debug */,
58B511EE1A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTAnimation" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511F01A9E6C8500147676 /* Debug */,
58B511F11A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
}

View File

@ -0,0 +1,22 @@
/**
* Copyright (c) 2015-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.
*/
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import "RCTDefines.h"
RCT_EXTERN CGFloat RCTInterpolateValue(CGFloat value,
CGFloat fromMin,
CGFloat fromMax,
CGFloat toMin,
CGFloat toMax);
RCT_EXTERN CGFloat RCTRadiansToDegrees(CGFloat radians);
RCT_EXTERN CGFloat RCTDegreesToRadians(CGFloat degrees);

View File

@ -0,0 +1,32 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTAnimationUtils.h"
/**
* Interpolates value by remapping it linearly fromMin->fromMax to toMin->toMax
*/
CGFloat RCTInterpolateValue(CGFloat value,
CGFloat fromMin,
CGFloat fromMax,
CGFloat toMin,
CGFloat toMax)
{
return toMin + (value - fromMin) * (toMax - toMin) / (fromMax - fromMin);
}
CGFloat RCTRadiansToDegrees(CGFloat radians)
{
return radians * 180.0 / M_PI;
}
CGFloat RCTDegreesToRadians(CGFloat degrees)
{
return degrees / 180.0 * M_PI;
}

View File

@ -0,0 +1,13 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTBridgeModule.h"
@interface RCTNativeAnimatedModule : NSObject <RCTBridgeModule>
@end

View File

@ -0,0 +1,247 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTNativeAnimatedModule.h"
#import "RCTAdditionAnimatedNode.h"
#import "RCTAnimationDriverNode.h"
#import "RCTAnimationUtils.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTInterpolationAnimatedNode.h"
#import "RCTLog.h"
#import "RCTMultiplicationAnimatedNode.h"
#import "RCTPropsAnimatedNode.h"
#import "RCTStyleAnimatedNode.h"
#import "RCTTransformAnimatedNode.h"
#import "RCTValueAnimatedNode.h"
@implementation RCTNativeAnimatedModule
{
NSMutableDictionary<NSNumber *, RCTAnimatedNode *> *_animationNodes;
NSMutableDictionary<NSNumber *, RCTAnimationDriverNode *> *_animationDrivers;
NSMutableSet<RCTAnimationDriverNode *> *_activeAnimations;
NSMutableSet<RCTAnimationDriverNode *> *_finishedAnimations;
NSMutableSet<RCTValueAnimatedNode *> *_updatedValueNodes;
NSMutableSet<RCTPropsAnimatedNode *> *_propAnimationNodes;
CADisplayLink *_displayLink;
}
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE()
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
_animationNodes = [NSMutableDictionary new];
_animationDrivers = [NSMutableDictionary new];
_activeAnimations = [NSMutableSet new];
_finishedAnimations = [NSMutableSet new];
_updatedValueNodes = [NSMutableSet new];
_propAnimationNodes = [NSMutableSet new];
}
RCT_EXPORT_METHOD(createAnimatedNode:(nonnull NSNumber *)tag
config:(NSDictionary<NSString *, id> *)config)
{
static NSDictionary *map;
static dispatch_once_t mapToken;
dispatch_once(&mapToken, ^{
map = @{@"style" : [RCTStyleAnimatedNode class],
@"value" : [RCTValueAnimatedNode class],
@"props" : [RCTPropsAnimatedNode class],
@"interpolation" : [RCTInterpolationAnimatedNode class],
@"addition" : [RCTAdditionAnimatedNode class],
@"multiplication" : [RCTMultiplicationAnimatedNode class],
@"transform" : [RCTTransformAnimatedNode class]};
});
NSString *nodeType = [RCTConvert NSString:config[@"type"]];
Class nodeClass = map[nodeType];
if (!nodeClass) {
RCTLogError(@"Animated node type %@ not supported natively", nodeType);
return;
}
RCTAnimatedNode *node = [[nodeClass alloc] initWithTag:tag config:config];
_animationNodes[tag] = node;
if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) {
[_propAnimationNodes addObject:(RCTPropsAnimatedNode *)node];
}
}
RCT_EXPORT_METHOD(connectAnimatedNodes:(nonnull NSNumber *)parentTag
childTag:(nonnull NSNumber *)childTag)
{
RCTAssertParam(parentTag);
RCTAssertParam(childTag);
RCTAnimatedNode *parentNode = _animationNodes[parentTag];
RCTAnimatedNode *childNode = _animationNodes[childTag];
RCTAssertParam(parentNode);
RCTAssertParam(childNode);
[parentNode addChild:childNode];
}
RCT_EXPORT_METHOD(disconnectAnimatedNodes:(nonnull NSNumber *)parentTag
childTag:(nonnull NSNumber *)childTag)
{
RCTAssertParam(parentTag);
RCTAssertParam(childTag);
RCTAnimatedNode *parentNode = _animationNodes[parentTag];
RCTAnimatedNode *childNode = _animationNodes[childTag];
RCTAssertParam(parentNode);
RCTAssertParam(childNode);
[parentNode removeChild:childNode];
}
RCT_EXPORT_METHOD(startAnimatingNode:(nonnull NSNumber *)animationId
nodeTag:(nonnull NSNumber *)nodeTag
config:(NSDictionary<NSString *, id> *)config
endCallback:(RCTResponseSenderBlock)callBack)
{
if (RCT_DEBUG && ![config[@"type"] isEqual:@"frames"]) {
RCTLogError(@"Unsupported animation type: %@", config[@"type"]);
return;
}
NSTimeInterval delay = [RCTConvert double:config[@"delay"]];
NSNumber *toValue = [RCTConvert NSNumber:config[@"toValue"]] ?: @1;
NSArray<NSNumber *> *frames = [RCTConvert NSNumberArray:config[@"frames"]];
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)_animationNodes[nodeTag];
RCTAnimationDriverNode *animationDriver =
[[RCTAnimationDriverNode alloc] initWithId:animationId
delay:delay
toValue:toValue.doubleValue
frames:frames
forNode:valueNode
callBack:callBack];
[_activeAnimations addObject:animationDriver];
_animationDrivers[animationId] = animationDriver;
[animationDriver startAnimation];
[self startAnimation];
}
RCT_EXPORT_METHOD(stopAnimation:(nonnull NSNumber *)animationId)
{
RCTAnimationDriverNode *driver = _animationDrivers[animationId];
if (driver) {
[driver removeAnimation];
[_animationDrivers removeObjectForKey:animationId];
[_activeAnimations removeObject:driver];
[_finishedAnimations removeObject:driver];
}
}
RCT_EXPORT_METHOD(setAnimatedNodeValue:(nonnull NSNumber *)nodeTag
value:(nonnull NSNumber *)value)
{
RCTAnimatedNode *node = _animationNodes[nodeTag];
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
RCTLogError(@"Not a value node.");
return;
}
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
valueNode.value = value.floatValue;
[valueNode setNeedsUpdate];
}
RCT_EXPORT_METHOD(connectAnimatedNodeToView:(nonnull NSNumber *)nodeTag
viewTag:(nonnull NSNumber *)viewTag)
{
RCTAnimatedNode *node = _animationNodes[nodeTag];
if (viewTag && [node isKindOfClass:[RCTPropsAnimatedNode class]]) {
[(RCTPropsAnimatedNode *)node connectToView:viewTag animatedModule:self];
}
}
RCT_EXPORT_METHOD(disconnectAnimatedNodeFromView:(nonnull NSNumber *)nodeTag
viewTag:(nonnull NSNumber *)viewTag)
{
RCTAnimatedNode *node = _animationNodes[nodeTag];
if (viewTag && node && [node isKindOfClass:[RCTPropsAnimatedNode class]]) {
[(RCTPropsAnimatedNode *)node disconnectFromView:viewTag];
}
}
RCT_EXPORT_METHOD(dropAnimatedNode:(nonnull NSNumber *)tag)
{
RCTAnimatedNode *node = _animationNodes[tag];
if (node) {
[node detachNode];
[_animationNodes removeObjectForKey:tag];
if ([node isKindOfClass:[RCTValueAnimatedNode class]]) {
[_updatedValueNodes removeObject:(RCTValueAnimatedNode *)node];
} else if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) {
[_propAnimationNodes removeObject:(RCTPropsAnimatedNode *)node];
}
}
}
#pragma mark -- Animation Loop
- (void)startAnimation
{
if (!_displayLink && _activeAnimations.count > 0) {
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateAnimations)];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
}
- (void)updateAnimations
{
// Step Current active animations
// This also recursively marks children nodes as needing update
for (RCTAnimationDriverNode *animationDriver in _activeAnimations) {
[animationDriver stepAnimation];
}
// Perform node updates for marked nodes.
// At this point all nodes that are in need of an update are properly marked as such.
for (RCTPropsAnimatedNode *propsNode in _propAnimationNodes) {
[propsNode updateNodeIfNecessary];
}
// Cleanup nodes and prepare for next cycle. Remove updated nodes from bucket.
for (RCTAnimationDriverNode *driverNode in _activeAnimations) {
[driverNode cleanupAnimationUpdate];
}
for (RCTValueAnimatedNode *valueNode in _updatedValueNodes) {
[valueNode cleanupAnimationUpdate];
}
[_updatedValueNodes removeAllObjects];
for (RCTAnimationDriverNode *driverNode in _activeAnimations) {
if (driverNode.animationHasFinished) {
[driverNode removeAnimation];
[_finishedAnimations addObject:driverNode];
}
}
for (RCTAnimationDriverNode *driverNode in _finishedAnimations) {
[_activeAnimations removeObject:driverNode];
[_animationDrivers removeObjectForKey:driverNode.animationId];
}
[_finishedAnimations removeAllObjects];
if (_activeAnimations.count == 0) {
[_displayLink invalidate];
_displayLink = nil;
}
}
@end

View File

@ -0,0 +1,22 @@
/**
* Copyright (c) 2015-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.
*/
#import <Foundation/Foundation.h>
@class RCTNativeAnimatedModule;
@interface RCTViewPropertyMapper : NSObject
@property (nonatomic, readonly) NSNumber *viewTag;
- (instancetype)initWithViewTag:(NSNumber *)viewTag
animationModule:(RCTNativeAnimatedModule *)animationModule NS_DESIGNATED_INITIALIZER;
- (void)updateViewWithDictionary:(NSDictionary<NSString *, NSNumber *> *)updates;
@end

View File

@ -0,0 +1,94 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTViewPropertyMapper.h"
#import <UIKit/UIKit.h>
#import "RCTBridge.h"
#import "RCTUIManager.h"
#import "RCTNativeAnimatedModule.h"
@implementation RCTViewPropertyMapper
{
CGFloat _translateX;
CGFloat _translateY;
CGFloat _scaleX;
CGFloat _scaleY;
CGFloat _rotation;
RCTNativeAnimatedModule *_animationModule;
}
- (instancetype)initWithViewTag:(NSNumber *)viewTag
animationModule:(RCTNativeAnimatedModule *)animationModule
{
if ((self = [super init])) {
_animationModule = animationModule;
_viewTag = viewTag;
_translateX = 0;
_translateY = 0;
_scaleX = 1;
_scaleY = 1;
_rotation = 0;
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)updateViewWithDictionary:(NSDictionary<NSString *, NSNumber *> *)updates
{
if (updates.count) {
UIView *view = [_animationModule.bridge.uiManager viewForReactTag:_viewTag];
if (!view) {
return;
}
NSNumber *opacity = updates[@"opacity"];
if (opacity) {
view.alpha = opacity.doubleValue;
}
NSNumber *scale = updates[@"scale"];
if (scale) {
_scaleX = scale.doubleValue;
_scaleY = scale.doubleValue;
}
NSNumber *scaleX = updates[@"scaleX"];
if (scaleX) {
_scaleX = scaleX.doubleValue;
}
NSNumber *scaleY = updates[@"scaleY"];
if (scaleY) {
_scaleY = scaleY.doubleValue;
}
NSNumber *translateX = updates[@"translateX"];
if (translateX) {
_translateX = translateX.doubleValue;
}
NSNumber *translateY = updates[@"translateY"];
if (translateY) {
_translateY = translateY.doubleValue;
}
NSNumber *rotation = updates[@"rotate"];
if (rotation) {
_rotation = rotation.doubleValue;
}
if (translateX || translateY || scale || scaleX || scaleY || rotation) {
CATransform3D xform = CATransform3DMakeScale(_scaleX, _scaleY, 0);
xform = CATransform3DTranslate(xform, _translateX, _translateY, 0);
xform = CATransform3DRotate(xform, _rotation, 0, 0, 1);
view.layer.allowsEdgeAntialiasing = YES;
view.layer.transform = xform;
}
}
}
@end