diff --git a/apps/tests/ui/animation/animation-tests.ts b/apps/tests/ui/animation/animation-tests.ts
index b75df1340..3b25f9447 100644
--- a/apps/tests/ui/animation/animation-tests.ts
+++ b/apps/tests/ui/animation/animation-tests.ts
@@ -1,6 +1,7 @@
import TKUnit = require("../../TKUnit");
import helper = require("../helper");
import pageModule = require("ui/page");
+import viewModule = require("ui/core/view");
import labelModule = require("ui/label");
import stackLayoutModule = require("ui/layouts/stack-layout");
import colorModule = require("color");
@@ -46,6 +47,7 @@ export var test_AnimatingProperties = function (done) {
.then(() => {
////console.log("Animation finished.");
//
+ assertIOSNativeTransformIsCorrect(label);
helper.goBack();
done();
//
@@ -85,6 +87,7 @@ export var test_CancellingAnimation = function (done) {
.then(() => {
////console.log("Animation finished");
//
+ assertIOSNativeTransformIsCorrect(label);
helper.goBack();
done();
//
@@ -130,6 +133,7 @@ export var test_ChainingAnimations = function (done) {
.then(() => {
////console.log("Animation finished");
//
+ assertIOSNativeTransformIsCorrect(label);
helper.goBack();
done();
//
@@ -176,6 +180,7 @@ export var test_ReusingAnimations = function (done) {
.then(() => {
////console.log("Animation finished");
//
+ assertIOSNativeTransformIsCorrect(label);
helper.goBack();
done();
//
@@ -227,6 +232,9 @@ export var test_AnimatingMultipleViews = function (done) {
.then(() => {
////console.log("Animations finished");
//
+ assertIOSNativeTransformIsCorrect(label1);
+ assertIOSNativeTransformIsCorrect(label2);
+ assertIOSNativeTransformIsCorrect(label3);
helper.goBack();
done();
//
@@ -319,6 +327,7 @@ export var test_AnimateTranslate = function (done) {
.then(() => {
TKUnit.assert(label.translateX === 100);
TKUnit.assert(label.translateY === 200);
+ assertIOSNativeTransformIsCorrect(label);
helper.goBack();
done();
})
@@ -348,6 +357,7 @@ export var test_AnimateScale = function (done) {
.then(() => {
TKUnit.assert(label.scaleX === 2);
TKUnit.assert(label.scaleY === 3);
+ assertIOSNativeTransformIsCorrect(label);
helper.goBack();
done();
})
@@ -376,6 +386,7 @@ export var test_AnimateRotate = function (done) {
label.animate({ rotate: 123 })
.then(() => {
TKUnit.assert(label.rotate === 123);
+ assertIOSNativeTransformIsCorrect(label);
helper.goBack();
done();
})
@@ -412,6 +423,7 @@ export var test_AnimateTranslateScaleAndRotateSimultaneously = function (done) {
TKUnit.assert(label.scaleX === 2);
TKUnit.assert(label.scaleY === 3);
TKUnit.assert(label.rotate === 123);
+ assertIOSNativeTransformIsCorrect(label);
helper.goBack();
done();
})
@@ -421,6 +433,53 @@ export var test_AnimateTranslateScaleAndRotateSimultaneously = function (done) {
});
}
+export var test_AnimateTranslateScaleAndRotateSequentially = function (done) {
+ var mainPage: pageModule.Page;
+ var label: labelModule.Label;
+ var pageFactory = function (): pageModule.Page {
+ label = new labelModule.Label();
+ label.text = "label";
+ var stackLayout = new stackLayoutModule.StackLayout();
+ stackLayout.addChild(label);
+ mainPage = new pageModule.Page();
+ mainPage.content = stackLayout;
+ return mainPage;
+ };
+
+ helper.navigate(pageFactory);
+ TKUnit.waitUntilReady(() => { return label.isLoaded });
+
+ label.animate({translate: { x: 100, y: 200 }})
+ .then(() => {
+ TKUnit.assert(label.translateX === 100);
+ TKUnit.assert(label.translateY === 200);
+ assertIOSNativeTransformIsCorrect(label);
+ return label.animate({ scale: { x: 2, y: 3 } });
+ })
+ .then(() => {
+ TKUnit.assert(label.translateX === 100);
+ TKUnit.assert(label.translateY === 200);
+ TKUnit.assert(label.scaleX === 2);
+ TKUnit.assert(label.scaleY === 3);
+ assertIOSNativeTransformIsCorrect(label);
+ return label.animate({ rotate: 123 });
+ })
+ .then(() => {
+ TKUnit.assert(label.translateX === 100);
+ TKUnit.assert(label.translateY === 200);
+ TKUnit.assert(label.scaleX === 2);
+ TKUnit.assert(label.scaleY === 3);
+ TKUnit.assert(label.rotate === 123);
+ assertIOSNativeTransformIsCorrect(label);
+ helper.goBack();
+ done();
+ })
+ .catch((e) => {
+ helper.goBack();
+ done(e);
+ });
+}
+
export var test_AnimationsAreAlwaysPlayed = function (done) {
var mainPage: pageModule.Page;
var label: labelModule.Label;
@@ -517,4 +576,13 @@ export var test_PlayPromiseIsRejectedWhenAnimationIsCancelled = function (done)
});
animation.cancel();
+}
+
+function assertIOSNativeTransformIsCorrect(view: viewModule.View) {
+ if (view.ios) {
+ var errorMessage = (animation)._getTransformMismatchErrorMessage(view);
+ if (errorMessage) {
+ TKUnit.assert(false, errorMessage);
+ }
+ }
}
\ No newline at end of file
diff --git a/ui/animation/animation.ios.ts b/ui/animation/animation.ios.ts
index 4a64ddb64..4d79f8d5c 100644
--- a/ui/animation/animation.ios.ts
+++ b/ui/animation/animation.ios.ts
@@ -1,5 +1,6 @@
import definition = require("ui/animation");
import common = require("./animation-common");
+import viewModule = require("ui/core/view");
import trace = require("trace");
global.moduleMerge(common, exports);
@@ -104,10 +105,11 @@ export class Animation extends common.Animation implements definition.Animation
trace.write(that._finishedAnimations + " animations finished.", trace.categories.Animation);
// Update our properties on the view.
- var i = 0;
+ // This should not change the native transform which is already updated by the animation itself.
+ var i;
var len = that._propertyAnimations.length;
var a: common.PropertyAnimation;
- for (; i < len; i++) {
+ for (i = 0; i < len; i++) {
a = that._propertyAnimations[i];
switch (a.property) {
case common.Properties.translate:
@@ -124,6 +126,15 @@ export class Animation extends common.Animation implements definition.Animation
}
}
+ // Validate that the properties of our view are aligned with the native transform matrix.
+ for (i = 0; i < len; i++) {
+ a = that._propertyAnimations[i];
+ var errorMessage = _getTransformMismatchErrorMessage(a.target);
+ if (errorMessage) {
+ throw new Error(errorMessage);
+ }
+ }
+
that._resolveAnimationFinishedPromise();
}
}
@@ -201,7 +212,7 @@ export class Animation extends common.Animation implements definition.Animation
case _transform:
originalValue = nativeView.transform;
(animation)._propertyResetCallback = () => { nativeView.transform = originalValue };
- nativeView.transform = animation.value;
+ nativeView.transform = Animation._createNativeAffineTransform(animation);
break;
default:
throw new Error("Cannot animate " + animation.property);
@@ -217,6 +228,43 @@ export class Animation extends common.Animation implements definition.Animation
}
}
+ private static _createNativeAffineTransform(animation: common.PropertyAnimation): CGAffineTransform {
+ var view = animation.target;
+ var value = animation.value;
+
+ trace.write("Creating native affine transform. Curent transform is: " + NSStringFromCGAffineTransform(view._nativeView.transform), trace.categories.Animation);
+
+ // Order is important: translate, rotate, scale
+ var result: CGAffineTransform = CGAffineTransformIdentity;
+ trace.write("Identity: " + NSStringFromCGAffineTransform(result), trace.categories.Animation);
+
+ if (value[common.Properties.translate] !== undefined) {
+ result = CGAffineTransformTranslate(result, value[common.Properties.translate].x, value[common.Properties.translate].y);
+ }
+ else {
+ result = CGAffineTransformTranslate(result, view.translateX, view.translateY);
+ }
+ trace.write("After translate: " + NSStringFromCGAffineTransform(result), trace.categories.Animation);
+
+ if (value[common.Properties.rotate] !== undefined) {
+ result = CGAffineTransformRotate(result, value[common.Properties.rotate] * Math.PI / 180);
+ }
+ else {
+ result = CGAffineTransformRotate(result, view.rotate * Math.PI / 180);
+ }
+ trace.write("After rotate: " + NSStringFromCGAffineTransform(result), trace.categories.Animation);
+
+ if (value[common.Properties.scale] !== undefined) {
+ result = CGAffineTransformScale(result, value[common.Properties.scale].x, value[common.Properties.scale].y);
+ }
+ else {
+ result = CGAffineTransformScale(result, view.scaleX, view.scaleY);
+ }
+ trace.write("After scale: " + NSStringFromCGAffineTransform(result), trace.categories.Animation);
+
+ return result;
+ }
+
private static _isAffineTransform(property: string): boolean {
return property === _transform
|| property === common.Properties.translate
@@ -236,20 +284,6 @@ export class Animation extends common.Animation implements definition.Animation
return result;
}
- private static _affineTransform(matrix: CGAffineTransform, property: string, value: any): CGAffineTransform {
- switch (property) {
- case common.Properties.translate:
- return CGAffineTransformTranslate(matrix, value.x, value.y);
- case common.Properties.rotate:
- return CGAffineTransformRotate(matrix, value * Math.PI / 180);
- case common.Properties.scale:
- return CGAffineTransformScale(matrix, value.x, value.y);
- default:
- throw new Error("Cannot create transform for" + property);
- break;
- }
- }
-
private static _mergeAffineTransformAnimations(propertyAnimations: Array): Array {
var result = new Array();
@@ -266,27 +300,31 @@ export class Animation extends common.Animation implements definition.Animation
result.push(propertyAnimations[i]);
}
else {
-
// This animation has not been merged anywhere. Create a new transform animation.
+ // The value becomes a JSON object combining all affine transforms together like this:
+ // {
+ // translate: {x: 100, y: 100 },
+ // rotate: 90,
+ // scale: {x: 2, y: 2 }
+ // }
var newTransformAnimation: common.PropertyAnimation = {
target: propertyAnimations[i].target,
property: _transform,
- value: Animation._affineTransform(CGAffineTransformIdentity, propertyAnimations[i].property, propertyAnimations[i].value),
+ value: {},
duration: propertyAnimations[i].duration,
delay: propertyAnimations[i].delay,
iterations: propertyAnimations[i].iterations
};
+ newTransformAnimation.value[propertyAnimations[i].property] = propertyAnimations[i].value;
trace.write("Created new transform animation: " + common.Animation._getAnimationInfo(newTransformAnimation), trace.categories.Animation);
+ // Merge all compatible affine transform animations to the right into this new animation.
j = i + 1;
if (j < length) {
- // Merge all compatible affine transform animations to the right into this new animation.
for (; j < length; j++) {
if (Animation._canBeMerged(propertyAnimations[i], propertyAnimations[j])) {
- trace.write("Merging animations: " + common.Animation._getAnimationInfo(newTransformAnimation) + " + " + common.Animation._getAnimationInfo(propertyAnimations[j]) + " = ", trace.categories.Animation);
- trace.write("New native transform is: " + NSStringFromCGAffineTransform(newTransformAnimation.value), trace.categories.Animation);
- newTransformAnimation.value = Animation._affineTransform(newTransformAnimation.value, propertyAnimations[j].property, propertyAnimations[j].value);
-
+ trace.write("Merging animations: " + common.Animation._getAnimationInfo(newTransformAnimation) + " + " + common.Animation._getAnimationInfo(propertyAnimations[j]) + ";", trace.categories.Animation);
+ newTransformAnimation.value[propertyAnimations[j].property] = propertyAnimations[j].value;
// Mark that it has been merged so we can skip it on our outer loop.
propertyAnimations[j][_skip] = true;
}
@@ -299,4 +337,21 @@ export class Animation extends common.Animation implements definition.Animation
return result;
}
+
+}
+
+export function _getTransformMismatchErrorMessage(view: viewModule.View): string {
+ // Order is important: translate, rotate, scale
+ var result: CGAffineTransform = CGAffineTransformIdentity;
+ result = CGAffineTransformTranslate(result, view.translateX, view.translateY);
+ result = CGAffineTransformRotate(result, view.rotate * Math.PI / 180);
+ result = CGAffineTransformScale(result, view.scaleX, view.scaleY);
+ var viewTransform = NSStringFromCGAffineTransform(result);
+ var nativeTransform = NSStringFromCGAffineTransform(view._nativeView.transform);
+
+ if (viewTransform !== nativeTransform) {
+ return "View and Native transforms do not match. View: " + viewTransform + "; Native: " + nativeTransform;
+ }
+
+ return undefined;
}