Maintain transform order

Summary:
This diff addresses the issues raised by kmagiera in https://github.com/facebook/react-native/pull/7884. Transforms should be applied in the order they are defined, just like in `processTransform.js`. A scale applied before a translation, for instance, should give a different result than a translation applied before a scale.

We leverage CATransform3D to do the heavy lifting. A concatenated transform is passed all the way to `RCTViewPropertyMapper`. It is compared with the transform currently applied to the view, and if different, applied. The same approach is used for opacity.

I think it makes the most sense to do this diffing in `RCTViewPropertyMapper`, as opposed to creating and cleaning up an `_updatedPropsDictionary` each frame in `RCTTransformAnimatedNode` and `RCTStyleAnimatedNode`. The node should keep its full value; applying a minimal set of altered props is an optimization.  The higher up this optimization is implemented, the more assumptions it makes. e.g. that there will only ever be a sing
Closes https://github.com/facebook/react-native/pull/9050

Differential Revision: D3658139

fbshipit-source-id: ad6286762ef734084cbdf83c9bd9241190302d34
This commit is contained in:
Ryan Gomba
2016-08-02 14:23:18 -07:00
committed by Facebook Github Bot
parent b83ccb5749
commit eb96b7fabc
8 changed files with 81 additions and 70 deletions

View File

@@ -1160,21 +1160,24 @@ class AnimatedTransform extends AnimatedWithChildren {
}
__getNativeConfig(): any {
var transConfig = {};
var transConfigs = [];
this._transforms.forEach(transform => {
for (var key in transform) {
var value = transform[key];
if (value instanceof Animated) {
transConfig[key] = value.__getNativeTag();
transConfigs.push({
property: key,
nodeTag: value.__getNativeTag(),
});
}
}
});
NativeAnimatedHelper.validateTransform(transConfig);
NativeAnimatedHelper.validateTransform(transConfigs);
return {
type: 'transform',
transform: transConfig,
transforms: transConfigs,
};
}
}

View File

@@ -87,7 +87,12 @@ var TRANSFORM_WHITELIST = {
translateX: true,
translateY: true,
scale: true,
scaleX: true,
scaleY: true,
rotate: true,
rotateX: true,
rotateY: true,
perspective: true,
};
function validateProps(params: Object): void {
@@ -98,12 +103,12 @@ 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 validateTransform(configs: Array<Object>): void {
configs.forEach((config) => {
if (!TRANSFORM_WHITELIST.hasOwnProperty(config.property)) {
throw new Error(`Property '${config.property}' is not supported by native animated module`);
}
}
});
}
function validateStyles(styles: Object): void {

View File

@@ -11,6 +11,6 @@
@interface RCTStyleAnimatedNode : RCTAnimatedNode
- (NSDictionary<NSString *, NSNumber *> *)updatedPropsDictionary;
- (NSDictionary<NSString *, NSObject *> *)updatedPropsDictionary;
@end

View File

@@ -14,7 +14,7 @@
@implementation RCTStyleAnimatedNode
{
NSMutableDictionary<NSString *, NSNumber *> *_updatedPropsDictionary;
NSMutableDictionary<NSString *, NSObject *> *_updatedPropsDictionary;
}
- (instancetype)initWithTag:(NSNumber *)tag

View File

@@ -11,6 +11,6 @@
@interface RCTTransformAnimatedNode : RCTAnimatedNode
- (NSDictionary<NSString *, NSNumber *> *)updatedPropsDictionary;
- (NSDictionary<NSString *, NSObject *> *)updatedPropsDictionary;
@end

View File

@@ -12,7 +12,7 @@
@implementation RCTTransformAnimatedNode
{
NSMutableDictionary<NSString *, NSNumber *> *_updatedPropsDictionary;
NSMutableDictionary<NSString *, NSObject *> *_updatedPropsDictionary;
}
- (instancetype)initWithTag:(NSNumber *)tag
@@ -33,14 +33,50 @@
{
[super performUpdate];
NSDictionary<NSString *, NSNumber *> *transforms = self.config[@"transform"];
[transforms enumerateKeysAndObjectsUsingBlock:^(NSString *property, NSNumber *nodeTag, __unused BOOL *stop) {
CATransform3D transform = CATransform3DIdentity;
NSArray<NSDictionary *> *transformConfigs = self.config[@"transforms"];
for (NSDictionary *transformConfig in transformConfigs) {
NSNumber *nodeTag = transformConfig[@"nodeTag"];
RCTAnimatedNode *node = self.parentNodes[nodeTag];
if (node.hasUpdated && [node isKindOfClass:[RCTValueAnimatedNode class]]) {
RCTValueAnimatedNode *parentNode = (RCTValueAnimatedNode *)node;
self->_updatedPropsDictionary[property] = @(parentNode.value);
NSString *property = transformConfig[@"property"];
CGFloat value = parentNode.value;
if ([property isEqualToString:@"scale"]) {
transform = CATransform3DScale(transform, value, value, 1);
} else if ([property isEqualToString:@"scaleX"]) {
transform = CATransform3DScale(transform, value, 1, 1);
} else if ([property isEqualToString:@"scaleY"]) {
transform = CATransform3DScale(transform, 1, value, 1);
} else if ([property isEqualToString:@"translateX"]) {
transform = CATransform3DTranslate(transform, value, 0, 0);
} else if ([property isEqualToString:@"translateY"]) {
transform = CATransform3DTranslate(transform, 0, value, 0);
} else if ([property isEqualToString:@"rotate"]) {
transform = CATransform3DRotate(transform, value, 0, 0, 1);
} else if ([property isEqualToString:@"rotateX"]) {
transform = CATransform3DRotate(transform, value, 1, 0, 0);
} else if ([property isEqualToString:@"rotateY"]) {
transform = CATransform3DRotate(transform, value, 0, 1, 0);
} else if ([property isEqualToString:@"perspective"]) {
transform.m34 = 1.0 / -value;
}
}
}];
}
_updatedPropsDictionary[@"transform"] = [NSValue valueWithCATransform3D:transform];
}
- (void)cleanupAnimationUpdate

View File

@@ -6,6 +6,7 @@
* 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;
@@ -17,6 +18,6 @@
- (instancetype)initWithViewTag:(NSNumber *)viewTag
animationModule:(RCTNativeAnimatedModule *)animationModule NS_DESIGNATED_INITIALIZER;
- (void)updateViewWithDictionary:(NSDictionary<NSString *, NSNumber *> *)updates;
- (void)updateViewWithDictionary:(NSDictionary<NSString *, NSObject *> *)updates;
@end

View File

@@ -12,16 +12,12 @@
#import <UIKit/UIKit.h>
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTUIManager.h"
#import "RCTNativeAnimatedModule.h"
@implementation RCTViewPropertyMapper
{
CGFloat _translateX;
CGFloat _translateY;
CGFloat _scaleX;
CGFloat _scaleY;
CGFloat _rotation;
RCTNativeAnimatedModule *_animationModule;
}
@@ -31,63 +27,33 @@
if ((self = [super init])) {
_animationModule = animationModule;
_viewTag = viewTag;
_translateX = 0;
_translateY = 0;
_scaleX = 1;
_scaleY = 1;
_rotation = 0;
_animationModule = animationModule;
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)updateViewWithDictionary:(NSDictionary<NSString *, NSNumber *> *)updates
- (void)updateViewWithDictionary:(NSDictionary<NSString *, NSObject *> *)updates
{
if (updates.count) {
UIView *view = [_animationModule.bridge.uiManager viewForReactTag:_viewTag];
if (!view) {
return;
}
if (!updates.count) {
return;
}
NSNumber *opacity = updates[@"opacity"];
if (opacity) {
view.alpha = opacity.doubleValue;
}
UIView *view = [_animationModule.bridge.uiManager viewForReactTag:_viewTag];
if (!view) {
return;
}
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;
}
NSNumber *opacity = [RCTConvert NSNumber:updates[@"opacity"]];
if (opacity) {
view.alpha = opacity.floatValue;
}
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;
}
NSObject *transform = updates[@"transform"];
if ([transform isKindOfClass:[NSValue class]]) {
view.layer.allowsEdgeAntialiasing = YES;
view.layer.transform = ((NSValue *)transform).CATransform3DValue;
}
}