mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-16 11:42:04 +08:00
Fixed #801: Chained animations lose state on iOS.
This commit is contained in:
@ -1,6 +1,7 @@
|
|||||||
import TKUnit = require("../../TKUnit");
|
import TKUnit = require("../../TKUnit");
|
||||||
import helper = require("../helper");
|
import helper = require("../helper");
|
||||||
import pageModule = require("ui/page");
|
import pageModule = require("ui/page");
|
||||||
|
import viewModule = require("ui/core/view");
|
||||||
import labelModule = require("ui/label");
|
import labelModule = require("ui/label");
|
||||||
import stackLayoutModule = require("ui/layouts/stack-layout");
|
import stackLayoutModule = require("ui/layouts/stack-layout");
|
||||||
import colorModule = require("color");
|
import colorModule = require("color");
|
||||||
@ -46,6 +47,7 @@ export var test_AnimatingProperties = function (done) {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
////console.log("Animation finished.");
|
////console.log("Animation finished.");
|
||||||
// <hide>
|
// <hide>
|
||||||
|
assertIOSNativeTransformIsCorrect(label);
|
||||||
helper.goBack();
|
helper.goBack();
|
||||||
done();
|
done();
|
||||||
// </hide>
|
// </hide>
|
||||||
@ -85,6 +87,7 @@ export var test_CancellingAnimation = function (done) {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
////console.log("Animation finished");
|
////console.log("Animation finished");
|
||||||
// <hide>
|
// <hide>
|
||||||
|
assertIOSNativeTransformIsCorrect(label);
|
||||||
helper.goBack();
|
helper.goBack();
|
||||||
done();
|
done();
|
||||||
// </hide>
|
// </hide>
|
||||||
@ -130,6 +133,7 @@ export var test_ChainingAnimations = function (done) {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
////console.log("Animation finished");
|
////console.log("Animation finished");
|
||||||
// <hide>
|
// <hide>
|
||||||
|
assertIOSNativeTransformIsCorrect(label);
|
||||||
helper.goBack();
|
helper.goBack();
|
||||||
done();
|
done();
|
||||||
// </hide>
|
// </hide>
|
||||||
@ -176,6 +180,7 @@ export var test_ReusingAnimations = function (done) {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
////console.log("Animation finished");
|
////console.log("Animation finished");
|
||||||
// <hide>
|
// <hide>
|
||||||
|
assertIOSNativeTransformIsCorrect(label);
|
||||||
helper.goBack();
|
helper.goBack();
|
||||||
done();
|
done();
|
||||||
// </hide>
|
// </hide>
|
||||||
@ -227,6 +232,9 @@ export var test_AnimatingMultipleViews = function (done) {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
////console.log("Animations finished");
|
////console.log("Animations finished");
|
||||||
// <hide>
|
// <hide>
|
||||||
|
assertIOSNativeTransformIsCorrect(label1);
|
||||||
|
assertIOSNativeTransformIsCorrect(label2);
|
||||||
|
assertIOSNativeTransformIsCorrect(label3);
|
||||||
helper.goBack();
|
helper.goBack();
|
||||||
done();
|
done();
|
||||||
// </hide>
|
// </hide>
|
||||||
@ -319,6 +327,7 @@ export var test_AnimateTranslate = function (done) {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
TKUnit.assert(label.translateX === 100);
|
TKUnit.assert(label.translateX === 100);
|
||||||
TKUnit.assert(label.translateY === 200);
|
TKUnit.assert(label.translateY === 200);
|
||||||
|
assertIOSNativeTransformIsCorrect(label);
|
||||||
helper.goBack();
|
helper.goBack();
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
@ -348,6 +357,7 @@ export var test_AnimateScale = function (done) {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
TKUnit.assert(label.scaleX === 2);
|
TKUnit.assert(label.scaleX === 2);
|
||||||
TKUnit.assert(label.scaleY === 3);
|
TKUnit.assert(label.scaleY === 3);
|
||||||
|
assertIOSNativeTransformIsCorrect(label);
|
||||||
helper.goBack();
|
helper.goBack();
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
@ -376,6 +386,7 @@ export var test_AnimateRotate = function (done) {
|
|||||||
label.animate({ rotate: 123 })
|
label.animate({ rotate: 123 })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
TKUnit.assert(label.rotate === 123);
|
TKUnit.assert(label.rotate === 123);
|
||||||
|
assertIOSNativeTransformIsCorrect(label);
|
||||||
helper.goBack();
|
helper.goBack();
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
@ -412,6 +423,7 @@ export var test_AnimateTranslateScaleAndRotateSimultaneously = function (done) {
|
|||||||
TKUnit.assert(label.scaleX === 2);
|
TKUnit.assert(label.scaleX === 2);
|
||||||
TKUnit.assert(label.scaleY === 3);
|
TKUnit.assert(label.scaleY === 3);
|
||||||
TKUnit.assert(label.rotate === 123);
|
TKUnit.assert(label.rotate === 123);
|
||||||
|
assertIOSNativeTransformIsCorrect(label);
|
||||||
helper.goBack();
|
helper.goBack();
|
||||||
done();
|
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) {
|
export var test_AnimationsAreAlwaysPlayed = function (done) {
|
||||||
var mainPage: pageModule.Page;
|
var mainPage: pageModule.Page;
|
||||||
var label: labelModule.Label;
|
var label: labelModule.Label;
|
||||||
@ -517,4 +576,13 @@ export var test_PlayPromiseIsRejectedWhenAnimationIsCancelled = function (done)
|
|||||||
});
|
});
|
||||||
|
|
||||||
animation.cancel();
|
animation.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertIOSNativeTransformIsCorrect(view: viewModule.View) {
|
||||||
|
if (view.ios) {
|
||||||
|
var errorMessage = (<any>animation)._getTransformMismatchErrorMessage(view);
|
||||||
|
if (errorMessage) {
|
||||||
|
TKUnit.assert(false, errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import definition = require("ui/animation");
|
import definition = require("ui/animation");
|
||||||
import common = require("./animation-common");
|
import common = require("./animation-common");
|
||||||
|
import viewModule = require("ui/core/view");
|
||||||
import trace = require("trace");
|
import trace = require("trace");
|
||||||
|
|
||||||
global.moduleMerge(common, exports);
|
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);
|
trace.write(that._finishedAnimations + " animations finished.", trace.categories.Animation);
|
||||||
|
|
||||||
// Update our properties on the view.
|
// 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 len = that._propertyAnimations.length;
|
||||||
var a: common.PropertyAnimation;
|
var a: common.PropertyAnimation;
|
||||||
for (; i < len; i++) {
|
for (i = 0; i < len; i++) {
|
||||||
a = that._propertyAnimations[i];
|
a = that._propertyAnimations[i];
|
||||||
switch (a.property) {
|
switch (a.property) {
|
||||||
case common.Properties.translate:
|
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();
|
that._resolveAnimationFinishedPromise();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,7 +212,7 @@ export class Animation extends common.Animation implements definition.Animation
|
|||||||
case _transform:
|
case _transform:
|
||||||
originalValue = nativeView.transform;
|
originalValue = nativeView.transform;
|
||||||
(<any>animation)._propertyResetCallback = () => { nativeView.transform = originalValue };
|
(<any>animation)._propertyResetCallback = () => { nativeView.transform = originalValue };
|
||||||
nativeView.transform = animation.value;
|
nativeView.transform = Animation._createNativeAffineTransform(animation);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error("Cannot animate " + animation.property);
|
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 {
|
private static _isAffineTransform(property: string): boolean {
|
||||||
return property === _transform
|
return property === _transform
|
||||||
|| property === common.Properties.translate
|
|| property === common.Properties.translate
|
||||||
@ -236,20 +284,6 @@ export class Animation extends common.Animation implements definition.Animation
|
|||||||
return result;
|
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<common.PropertyAnimation>): Array<common.PropertyAnimation> {
|
private static _mergeAffineTransformAnimations(propertyAnimations: Array<common.PropertyAnimation>): Array<common.PropertyAnimation> {
|
||||||
var result = new Array<common.PropertyAnimation>();
|
var result = new Array<common.PropertyAnimation>();
|
||||||
|
|
||||||
@ -266,27 +300,31 @@ export class Animation extends common.Animation implements definition.Animation
|
|||||||
result.push(propertyAnimations[i]);
|
result.push(propertyAnimations[i]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
// This animation has not been merged anywhere. Create a new transform animation.
|
// 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 = {
|
var newTransformAnimation: common.PropertyAnimation = {
|
||||||
target: propertyAnimations[i].target,
|
target: propertyAnimations[i].target,
|
||||||
property: _transform,
|
property: _transform,
|
||||||
value: Animation._affineTransform(CGAffineTransformIdentity, propertyAnimations[i].property, propertyAnimations[i].value),
|
value: {},
|
||||||
duration: propertyAnimations[i].duration,
|
duration: propertyAnimations[i].duration,
|
||||||
delay: propertyAnimations[i].delay,
|
delay: propertyAnimations[i].delay,
|
||||||
iterations: propertyAnimations[i].iterations
|
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);
|
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;
|
j = i + 1;
|
||||||
if (j < length) {
|
if (j < length) {
|
||||||
// Merge all compatible affine transform animations to the right into this new animation.
|
|
||||||
for (; j < length; j++) {
|
for (; j < length; j++) {
|
||||||
if (Animation._canBeMerged(propertyAnimations[i], propertyAnimations[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("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[propertyAnimations[j].property] = propertyAnimations[j].value;
|
||||||
newTransformAnimation.value = Animation._affineTransform(newTransformAnimation.value, propertyAnimations[j].property, propertyAnimations[j].value);
|
|
||||||
|
|
||||||
// Mark that it has been merged so we can skip it on our outer loop.
|
// Mark that it has been merged so we can skip it on our outer loop.
|
||||||
propertyAnimations[j][_skip] = true;
|
propertyAnimations[j][_skip] = true;
|
||||||
}
|
}
|
||||||
@ -299,4 +337,21 @@ export class Animation extends common.Animation implements definition.Animation
|
|||||||
|
|
||||||
return result;
|
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;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user