diff --git a/apps/automated/src/ui/animation/animation-tests.ts b/apps/automated/src/ui/animation/animation-tests.ts index 7a96d2a7a..ad75839ce 100644 --- a/apps/automated/src/ui/animation/animation-tests.ts +++ b/apps/automated/src/ui/animation/animation-tests.ts @@ -79,7 +79,7 @@ export function test_PlayRejectsWhenAlreadyPlayingAnimation(done) { if (e === 'Animation is already playing.') { done(); } - }, + } ); } @@ -164,8 +164,8 @@ export function test_ChainingAnimations(done) { .then(() => label.animate({ translate: { x: 0, y: 0 }, duration: duration })) .then(() => label.animate({ scale: { x: 5, y: 5 }, duration: duration })) .then(() => label.animate({ scale: { x: 1, y: 1 }, duration: duration })) - .then(() => label.animate({ rotate: { x: 90, y: 0, z: 180 }, duration: duration })) - .then(() => label.animate({ rotate: { x: 0, y: 0, z: 0 }, duration: duration })) + .then(() => label.animate({ rotate: 180, duration: duration })) + .then(() => label.animate({ rotate: 0, duration: duration })) .then(() => { //console.log("Animation finished"); // >> (hide) @@ -610,7 +610,7 @@ export function test_PlayPromiseIsResolvedWhenAnimationFinishes(done) { function onRejected(e) { TKUnit.assert(false, 'Animation play promise should be resolved, not rejected.'); done(e); - }, + } ); } @@ -627,7 +627,7 @@ export function test_PlayPromiseIsRejectedWhenAnimationIsCancelled(done) { function onRejected(e) { TKUnit.assert(animation.isPlaying === false, 'Animation.isPlaying should be false when animation play promise is rejected.'); done(); - }, + } ); animation.cancel(); diff --git a/packages/core/ui/animation/index.ios.ts b/packages/core/ui/animation/index.ios.ts index 860ea7799..4f2ac5860 100644 --- a/packages/core/ui/animation/index.ios.ts +++ b/packages/core/ui/animation/index.ios.ts @@ -79,8 +79,8 @@ class AnimationDelegateImpl extends NSObject implements CAAnimationDelegate { targetStyle[setLocal ? widthProperty.name : widthProperty.keyframe] = value; break; case Properties.scale: - targetStyle[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = value.x === 0 ? 1e-6 : value.x; - targetStyle[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = value.y === 0 ? 1e-6 : value.y; + targetStyle[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = value.x === 0 ? 0.001 : value.x; + targetStyle[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = value.y === 0 ? 0.001 : value.y; break; case _transform: if (value[Properties.translate] !== undefined) { @@ -95,8 +95,8 @@ class AnimationDelegateImpl extends NSObject implements CAAnimationDelegate { if (value[Properties.scale] !== undefined) { const x = value[Properties.scale].x; const y = value[Properties.scale].y; - targetStyle[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = x === 0 ? 1e-6 : x; - targetStyle[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = y === 0 ? 1e-6 : y; + targetStyle[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = x === 0 ? 0.001 : x; + targetStyle[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = y === 0 ? 0.001 : y; } break; } @@ -309,6 +309,7 @@ export class Animation extends AnimationBase { const parent = view.parent as View; let propertyNameToAnimate = animation.property; + let subPropertyNameToAnimate; let toValue = animation.value; let fromValue; if (nativeView) { @@ -334,6 +335,8 @@ export class Animation extends AnimationBase { }; fromValue = nativeView.layer.opacity; break; + // In the case of rotation, avoid animating affine transform directly as it will animate to the closest result + // that is visually the same. For example, 0 -> 360 will leave view as is. case Properties.rotate: animation._originalValue = { x: view.rotateX, @@ -346,9 +349,30 @@ export class Animation extends AnimationBase { style[setLocal ? rotateYProperty.name : rotateYProperty.keyframe] = value.y; }; - propertyNameToAnimate = 'transform'; - fromValue = NSValue.valueWithCATransform3D(nativeView.layer.transform); - toValue = NSValue.valueWithCATransform3D(iosHelper.applyRotateTransform(nativeView.layer.transform, toValue.x, toValue.y, toValue.z)); + propertyNameToAnimate = 'transform.rotation'; + subPropertyNameToAnimate = ['x', 'y', 'z']; + fromValue = { + x: nativeView.layer.valueForKeyPath('transform.rotation.x'), + y: nativeView.layer.valueForKeyPath('transform.rotation.y'), + z: nativeView.layer.valueForKeyPath('transform.rotation.z'), + }; + + if (animation.target.rotateX !== undefined && animation.target.rotateX !== 0 && Math.floor(toValue / 360) - toValue / 360 === 0) { + fromValue.x = (animation.target.rotateX * Math.PI) / 180; + } + if (animation.target.rotateY !== undefined && animation.target.rotateY !== 0 && Math.floor(toValue / 360) - toValue / 360 === 0) { + fromValue.y = (animation.target.rotateY * Math.PI) / 180; + } + if (animation.target.rotate !== undefined && animation.target.rotate !== 0 && Math.floor(toValue / 360) - toValue / 360 === 0) { + fromValue.z = (animation.target.rotate * Math.PI) / 180; + } + + // Respect only value.z for back-compat until 3D rotations are implemented + toValue = { + x: (toValue.x * Math.PI) / 180, + y: (toValue.y * Math.PI) / 180, + z: (toValue.z * Math.PI) / 180, + }; break; case Properties.translate: animation._originalValue = { @@ -365,10 +389,10 @@ export class Animation extends AnimationBase { break; case Properties.scale: if (toValue.x === 0) { - toValue.x = 1e-6; + toValue.x = 0.001; } if (toValue.y === 0) { - toValue.y = 1e-6; + toValue.y = 0.001; } animation._originalValue = { x: view.scaleX, y: view.scaleY }; animation._propertyResetCallback = (value, valueSource) => { @@ -451,6 +475,7 @@ export class Animation extends AnimationBase { return { propertyNameToAnimate: propertyNameToAnimate, fromValue: fromValue, + subPropertiesToAnimate: subPropertyNameToAnimate, toValue: toValue, duration: duration, repeatCount: repeatCount, @@ -496,10 +521,10 @@ export class Animation extends AnimationBase { } private static _createGroupAnimation(args: AnimationInfo, animation: PropertyAnimation) { - const animations = NSMutableArray.alloc().initWithCapacity(args.subPropertiesToAnimate.length); const groupAnimation = CAAnimationGroup.new(); - groupAnimation.duration = args.duration; + const animations = NSMutableArray.alloc().initWithCapacity(args.subPropertiesToAnimate.length); + groupAnimation.duration = args.duration; if (args.repeatCount !== undefined) { groupAnimation.repeatCount = args.repeatCount; } @@ -651,30 +676,16 @@ export class Animation extends AnimationBase { } if (value[Properties.scale] !== undefined) { - const x = value[Properties.scale].x || 1e-6; - const y = value[Properties.scale].y || 1e-6; - result = CATransform3DScale(result, x, y, 1); - } - - if (value[Properties.rotate] !== undefined) { - const x = value[Properties.rotate].x; - const y = value[Properties.rotate].y; - const z = value[Properties.rotate].z; - const perspective = animation.target.perspective || 300; - - // Set perspective in case of rotation since we use z - if (x || y) { - result.m34 = -1 / perspective; - } - - result = iosHelper.applyRotateTransform(result, x, y, z); + const x = value[Properties.scale].x; + const y = value[Properties.scale].y; + result = CATransform3DScale(result, x === 0 ? 0.001 : x, y === 0 ? 0.001 : y, 1); } return result; } private static _isAffineTransform(property: string): boolean { - return property === _transform || property === Properties.translate || property === Properties.scale || property === Properties.rotate; + return property === _transform || property === Properties.translate || property === Properties.scale; } private static _canBeMerged(animation1: PropertyAnimation, animation2: PropertyAnimation) { @@ -941,8 +952,7 @@ function calculateTransform(view: View): CATransform3D { // Order is important: translate, rotate, scale let expectedTransform = new CATransform3D(CATransform3DIdentity); - // TODO: Add perspective property to transform animations (not just rotation) - // Set perspective in case of rotation since we use z + // Only set perspective if there is 3D rotation if (view.rotateX || view.rotateY) { expectedTransform.m34 = -1 / perspective; }