Merge pull request #1906 from NativeScript/raikov/fix-1905

Fixed: Visual states not working properly when the animation is canceled
This commit is contained in:
tzraikov
2016-04-08 12:42:41 +03:00
7 changed files with 145 additions and 90 deletions

View File

@ -15,8 +15,7 @@ let FLT_MAX = 340282346638528859811704183484516925440.000000;
declare var CASpringAnimation:any;
class AnimationInfo
{
class AnimationInfo {
public propertyNameToAnimate: string;
public fromValue: any;
public toValue: any;
@ -25,6 +24,21 @@ class AnimationInfo
public delay: number;
}
interface PropertyAnimationInfo extends common.PropertyAnimation {
_propertyResetCallback?: any;
_originalValue?: any;
}
interface AnimationDefinitionInternal extends definition.AnimationDefinition {
valueSource: number;
}
interface IOSView extends viewModule.View {
_suspendPresentationLayerUpdates();
_resumePresentationLayerUpdates();
_isPresentationLayerUpdateSuspeneded();
}
class AnimationDelegateImpl extends NSObject {
public nextAnimation: Function;
@ -43,74 +57,42 @@ class AnimationDelegateImpl extends NSObject {
animationDidStart(anim: CAAnimation): void {
let value = this._propertyAnimation.value;
(<any>this._propertyAnimation.target)._suspendPresentationLayerUpdates();
if (this._valueSource !== undefined) {
let valueSource = this._valueSource || dependencyObservable.ValueSource.Local;
let targetStyle = this._propertyAnimation.target.style;
(<IOSView>this._propertyAnimation.target)._suspendPresentationLayerUpdates();
switch (this._propertyAnimation.property) {
case common.Properties.backgroundColor:
targetStyle._setValue(style.backgroundColorProperty, value, this._valueSource);
targetStyle._setValue(style.backgroundColorProperty, value, valueSource);
break;
case common.Properties.opacity:
targetStyle._setValue(style.opacityProperty, value, this._valueSource);
targetStyle._setValue(style.opacityProperty, value, valueSource);
break;
case common.Properties.rotate:
targetStyle._setValue(style.rotateProperty, value, this._valueSource);
targetStyle._setValue(style.rotateProperty, value, valueSource);
break;
case common.Properties.translate:
targetStyle._setValue(style.translateXProperty, value.x, this._valueSource);
targetStyle._setValue(style.translateYProperty, value.y, this._valueSource);
targetStyle._setValue(style.translateXProperty, value.x, valueSource);
targetStyle._setValue(style.translateYProperty, value.y, valueSource);
break;
case common.Properties.scale:
targetStyle._setValue(style.scaleXProperty, value.x, this._valueSource);
targetStyle._setValue(style.scaleYProperty, value.y, this._valueSource);
targetStyle._setValue(style.scaleXProperty, value.x, valueSource);
targetStyle._setValue(style.scaleYProperty, value.y, valueSource);
break;
case _transform:
if (value[common.Properties.translate] !== undefined) {
targetStyle._setValue(style.translateXProperty, value[common.Properties.translate].x, this._valueSource);
targetStyle._setValue(style.translateYProperty, value[common.Properties.translate].y, this._valueSource);
targetStyle._setValue(style.translateXProperty, value[common.Properties.translate].x, valueSource);
targetStyle._setValue(style.translateYProperty, value[common.Properties.translate].y, valueSource);
}
if (value[common.Properties.scale] !== undefined) {
targetStyle._setValue(style.scaleXProperty, value[common.Properties.scale].x, this._valueSource);
targetStyle._setValue(style.scaleYProperty, value[common.Properties.scale].y, this._valueSource);
targetStyle._setValue(style.scaleXProperty, value[common.Properties.scale].x, valueSource);
targetStyle._setValue(style.scaleYProperty, value[common.Properties.scale].y, valueSource);
}
break;
}
}
else {
switch (this._propertyAnimation.property) {
case common.Properties.backgroundColor:
this._propertyAnimation.target.backgroundColor = value;
break;
case common.Properties.opacity:
this._propertyAnimation.target.opacity = value;
break;
case common.Properties.rotate:
this._propertyAnimation.target.rotate = value;
break;
case common.Properties.translate:
this._propertyAnimation.target.translateX = value.x;
this._propertyAnimation.target.translateY = value.y;
break;
case common.Properties.scale:
this._propertyAnimation.target.scaleX = value.x;
this._propertyAnimation.target.scaleY = value.y;
break;
case _transform:
if (value[common.Properties.translate] !== undefined) {
this._propertyAnimation.target.translateX = value[common.Properties.translate].x;
this._propertyAnimation.target.translateY = value[common.Properties.translate].y;
}
if (value[common.Properties.scale] !== undefined) {
this._propertyAnimation.target.scaleX = value[common.Properties.scale].x;
this._propertyAnimation.target.scaleY = value[common.Properties.scale].y;
}
break;
}
}
(<any>this._propertyAnimation.target)._resumePresentationLayerUpdates();
(<IOSView>this._propertyAnimation.target)._resumePresentationLayerUpdates();
}
public animationDidStopFinished(anim: CAAnimation, finished: boolean): void {
@ -127,7 +109,7 @@ export class Animation extends common.Animation implements definition.Animation
private _iOSAnimationFunction: Function;
private _finishedAnimations: number;
private _cancelledAnimations: number;
private _mergedPropertyAnimations: Array<common.PropertyAnimation>;
private _mergedPropertyAnimations: Array<PropertyAnimationInfo>;
private _valueSource: number;
public play(): definition.AnimationPromise {
@ -144,18 +126,19 @@ export class Animation extends common.Animation implements definition.Animation
let i = 0;
let length = this._mergedPropertyAnimations.length;
for (; i < length; i++) {
(<UIView>this._mergedPropertyAnimations[i].target._nativeView).layer.removeAllAnimations();
if ((<any>this._mergedPropertyAnimations[i])._propertyResetCallback) {
(<any>this._mergedPropertyAnimations[i])._propertyResetCallback((<any>this._mergedPropertyAnimations[i])._originalValue);
let propertyAnimation = this._mergedPropertyAnimations[i];
propertyAnimation.target._nativeView.layer.removeAllAnimations();
if (propertyAnimation._propertyResetCallback) {
propertyAnimation._propertyResetCallback(propertyAnimation._originalValue, this._valueSource);
}
}
}
constructor(animationDefinitions: Array<definition.AnimationDefinition>, playSequentially?: boolean) {
constructor(animationDefinitions: Array<AnimationDefinitionInternal>, playSequentially?: boolean) {
super(animationDefinitions, playSequentially);
if (animationDefinitions.length > 0 && (<any>animationDefinitions[0]).valueSource !== undefined) {
this._valueSource = (<any>animationDefinitions[0]).valueSource;
if (animationDefinitions.length > 0 && animationDefinitions[0].valueSource !== undefined) {
this._valueSource = animationDefinitions[0].valueSource;
}
if (!playSequentially) {
@ -222,7 +205,7 @@ export class Animation extends common.Animation implements definition.Animation
}
}
private static _getNativeAnimationArguments(animation: common.PropertyAnimation, valueSource: number): AnimationInfo {
private static _getNativeAnimationArguments(animation: PropertyAnimationInfo, valueSource: number): AnimationInfo {
let nativeView = <UIView>animation.target._nativeView;
let presentationLayer = nativeView.layer.presentationLayer();
@ -235,8 +218,10 @@ export class Animation extends common.Animation implements definition.Animation
switch (animation.property) {
case common.Properties.backgroundColor:
(<any>animation)._originalValue = animation.target.backgroundColor;
(<any>animation)._propertyResetCallback = (value) => { animation.target.backgroundColor = value; };
animation._originalValue = animation.target.backgroundColor;
animation._propertyResetCallback = (value, valueSource) => {
animation.target._setValue(style.backgroundColorProperty, value, valueSource);
};
if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) {
originalValue = presentationLayer.backgroundColor;
}
@ -250,8 +235,10 @@ export class Animation extends common.Animation implements definition.Animation
value = value.CGColor;
break;
case common.Properties.opacity:
(<any>animation)._originalValue = animation.target.opacity;
(<any>animation)._propertyResetCallback = (value) => { animation.target.opacity = value; };
animation._originalValue = animation.target.opacity;
animation._propertyResetCallback = (value, valueSource) => {
animation.target._setValue(style.opacityProperty, value, valueSource);
};
if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) {
originalValue = presentationLayer.opacity;
}
@ -260,8 +247,10 @@ export class Animation extends common.Animation implements definition.Animation
}
break;
case common.Properties.rotate:
(<any>animation)._originalValue = animation.target.rotate;
(<any>animation)._propertyResetCallback = (value) => { animation.target.rotate = value; };
animation._originalValue = animation.target.rotate;
animation._propertyResetCallback = (value, valueSource) => {
animation.target._setValue(style.rotateProperty, value, valueSource);
};
propertyNameToAnimate = "transform.rotation";
if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) {
originalValue = presentationLayer.valueForKeyPath("transform.rotation");
@ -276,8 +265,11 @@ export class Animation extends common.Animation implements definition.Animation
}
break;
case common.Properties.translate:
(<any>animation)._originalValue = { x:animation.target.translateX, y:animation.target.translateY };
(<any>animation)._propertyResetCallback = (value) => { animation.target.translateX = value.x; animation.target.translateY = value.y; };
animation._originalValue = { x: animation.target.translateX, y: animation.target.translateY };
animation._propertyResetCallback = (value, valueSource) => {
animation.target._setValue(style.translateXProperty, value.x, valueSource);
animation.target._setValue(style.translateYProperty, value.y, valueSource);
};
propertyNameToAnimate = "transform";
if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) {
originalValue = NSValue.valueWithCATransform3D(presentationLayer.transform);
@ -288,8 +280,11 @@ export class Animation extends common.Animation implements definition.Animation
value = NSValue.valueWithCATransform3D(CATransform3DTranslate(nativeView.layer.transform, value.x, value.y, 0));
break;
case common.Properties.scale:
(<any>animation)._originalValue = { x:animation.target.scaleX, y:animation.target.scaleY };
(<any>animation)._propertyResetCallback = (value) => { animation.target.scaleX = value.x; animation.target.scaleY = value.y; };
animation._originalValue = { x: animation.target.scaleX, y: animation.target.scaleY };
animation._propertyResetCallback = (value, valueSource) => {
animation.target._setValue(style.scaleXProperty, value.x, valueSource);
animation.target._setValue(style.scaleYProperty, value.y, valueSource);
};
propertyNameToAnimate = "transform";
if (presentationLayer != null && valueSource !== dependencyObservable.ValueSource.Css) {
originalValue = NSValue.valueWithCATransform3D(presentationLayer.transform);
@ -306,13 +301,13 @@ export class Animation extends common.Animation implements definition.Animation
else {
originalValue = NSValue.valueWithCATransform3D(nativeView.layer.transform);
}
(<any>animation)._originalValue = { xs:animation.target.scaleX, ys:animation.target.scaleY,
xt:animation.target.translateX, yt:animation.target.translateY };
(<any>animation)._propertyResetCallback = (value) => {
animation.target.translateX = value.xt;
animation.target.translateY = value.yt;
animation.target.scaleX = value.xs;
animation.target.scaleY = value.ys;
animation._originalValue = { xs: animation.target.scaleX, ys: animation.target.scaleY,
xt: animation.target.translateX, yt: animation.target.translateY };
animation._propertyResetCallback = (value, valueSource) => {
animation.target._setValue(style.translateXProperty, value.xt, valueSource);
animation.target._setValue(style.translateYProperty, value.yt, valueSource);
animation.target._setValue(style.scaleXProperty, value.xs, valueSource);
animation.target._setValue(style.scaleYProperty, value.ys, valueSource);
};
propertyNameToAnimate = "transform";
value = NSValue.valueWithCATransform3D(Animation._createNativeAffineTransform(animation));
@ -385,7 +380,7 @@ export class Animation extends common.Animation implements definition.Animation
}
}
private static _createNativeSpringAnimation(propertyAnimations: Array<common.PropertyAnimation>, index: number, playSequentially: boolean, args: AnimationInfo, animation: common.PropertyAnimation, valueSource: number, finishedCallback: (cancelled?: boolean) => void) {
private static _createNativeSpringAnimation(propertyAnimations: Array<PropertyAnimationInfo>, index: number, playSequentially: boolean, args: AnimationInfo, animation: PropertyAnimationInfo, valueSource: number, finishedCallback: (cancelled?: boolean) => void) {
let nativeView = <UIView>animation.target._nativeView;
@ -424,9 +419,9 @@ export class Animation extends common.Animation implements definition.Animation
nativeView.layer.setValueForKey(args.toValue, args.propertyNameToAnimate);
break;
case _transform:
(<any>animation)._originalValue = nativeView.layer.transform;
animation._originalValue = nativeView.layer.transform;
nativeView.layer.setValueForKey(args.toValue, args.propertyNameToAnimate);
(<any>animation)._propertyResetCallback = function (value) {
animation._propertyResetCallback = function (value) {
nativeView.layer.transform = value;
}
break;
@ -445,8 +440,8 @@ export class Animation extends common.Animation implements definition.Animation
}
}
else {
if ((<any>animation)._propertyResetCallback) {
(<any>animation)._propertyResetCallback((<any>animation)._originalValue);
if (animation._propertyResetCallback) {
animation._propertyResetCallback(animation._originalValue);
}
}
if (finishedCallback) {
@ -461,7 +456,7 @@ export class Animation extends common.Animation implements definition.Animation
private static _createNativeAffineTransform(animation: common.PropertyAnimation): CATransform3D {
let value = animation.value;
let result:CATransform3D = CATransform3DIdentity;
let result: CATransform3D = CATransform3DIdentity;
if (value[common.Properties.translate] !== undefined) {
let x = value[common.Properties.translate].x;

View File

@ -86,6 +86,11 @@ import view = require("ui/core/view");
*/
public play: (view: view.View) => Promise<void>;
/**
* Cancels a playing animation.
*/
public cancel: () => void;
/**
* Creates a keyframe animation from animation definition.
*/

View File

@ -1,4 +1,5 @@
import definition = require("ui/animation/keyframe-animation");
import animationModule = require("ui/animation");
import view = require("ui/core/view");
import enums = require("ui/enums");
import style = require("ui/styling/style");
@ -34,6 +35,7 @@ export class KeyframeAnimation {
private _reject;
private _isPlaying: boolean;
private _isForwards: boolean;
private _currentAnimation: animationModule.Animation;
public static keyframeAnimationFromInfo(info: KeyframeAnimationInfo, valueSourceModifier: number) {
let animations = new Array<Object>();
@ -98,6 +100,16 @@ export class KeyframeAnimation {
return this._isPlaying;
}
public cancel() {
if (this._isPlaying) {
if (this._currentAnimation && this._currentAnimation.isPlaying) {
this._currentAnimation.cancel();
}
this._isPlaying = false;
this._rejectAnimationFinishedPromise();
}
}
public play(view: view.View): Promise<void> {
if (this._isPlaying) {
throw new Error("Animation is already playing.");
@ -178,19 +190,25 @@ export class KeyframeAnimation {
}
}
else {
view.animate(this.animations[index]).then(() => {
let animationDef = this.animations[index];
(<any>animationDef).target = view;
let animation = new animationModule.Animation([animationDef]);
animation.play().then(() => {
this.animate(view, index + 1, iterations);
});
this._currentAnimation = animation;
}
}
public _resolveAnimationFinishedPromise() {
this._isPlaying = false;
this._currentAnimation = undefined;
this._resolve();
}
public _rejectAnimationFinishedPromise() {
this._isPlaying = false;
this._currentAnimation = undefined;
this._reject(new Error("Animation cancelled."));
}
}

View File

@ -9,6 +9,7 @@ import enums = require("ui/enums");
import utils = require("utils/utils");
import color = require("color");
import observable = require("data/observable");
import keyframeAnimationModule = require("ui/animation/keyframe-animation");
import {PropertyMetadata, ProxyObject} from "ui/core/proxy";
import {PropertyMetadataSettings, PropertyChangeData, Property, ValueSource, PropertyMetadata as doPropertyMetadata} from "ui/core/dependency-observable";
import {registerSpecialProperty} from "ui/builder/special-properties";
@ -174,6 +175,8 @@ export class View extends ProxyObject implements definition.View {
private _isLoaded: boolean;
private _isLayoutValid: boolean = false;
private _registeredAnimations: Array<keyframeAnimationModule.KeyframeAnimation>;
public _domId: number;
public _isAddedToNativeVisualTree = false;
@ -1170,12 +1173,36 @@ export class View extends ProxyObject implements definition.View {
}
public createAnimation(animation: any): any {
var animationModule: typeof animModule = require("ui/animation");
var that = this;
let animationModule: typeof animModule = require("ui/animation");
let that = this;
animation.target = that;
return new animationModule.Animation([animation]);
}
public _registerAnimation(animation: keyframeAnimationModule.KeyframeAnimation) {
if (this._registeredAnimations === undefined) {
this._registeredAnimations = new Array<keyframeAnimationModule.KeyframeAnimation>();
}
this._registeredAnimations.push(animation);
}
public _unregisterAnimation(animation: keyframeAnimationModule.KeyframeAnimation) {
if (this._registeredAnimations) {
let index = this._registeredAnimations.indexOf(animation);
if (index >= 0) {
this._registeredAnimations.splice(index, 1);
}
}
}
public _unregisterAllAnimations() {
if (this._registeredAnimations) {
for (let animation of this._registeredAnimations) {
animation.cancel();
}
}
}
public toString(): string {
var str = this.typeName;
if (this.id) {

5
ui/core/view.d.ts vendored
View File

@ -6,6 +6,7 @@ declare module "ui/core/view" {
import color = require("color");
import observable = require("data/observable");
import animation = require("ui/animation");
import keyframeAnimationModule = require("ui/animation/keyframe-animation");
/**
* Gets a child view by id.
@ -574,6 +575,10 @@ declare module "ui/core/view" {
_domId: number;
_cssClasses: Array<string>;
_registerAnimation(animation: keyframeAnimationModule.KeyframeAnimation);
_unregisterAnimation(animation: keyframeAnimationModule.KeyframeAnimation);
_unregisterAllAnimations();
_isAddedToNativeVisualTree: boolean;
/**

View File

@ -90,9 +90,12 @@ export class CssSelector {
});
if (this.animations && view.isLoaded) {
for (let animationInfo of this.animations) {
let realAnimation = keyframeAnimation.KeyframeAnimation.keyframeAnimationFromInfo(animationInfo, modifier);
if (realAnimation) {
realAnimation.play(view);
let animation = keyframeAnimation.KeyframeAnimation.keyframeAnimationFromInfo(animationInfo, modifier);
if (animation) {
view._registerAnimation(animation);
animation.play(view)
.then(() => { view._unregisterAnimation(animation); })
.catch((e) => { view._unregisterAnimation(animation); });
}
}
}

View File

@ -83,6 +83,8 @@ function resetProperties(view: viewModule.View, oldState: VisualState, newState:
}
}
view._unregisterAllAnimations();
for (let selector of oldState.animatedSelectors) {
for (let animationInfo of selector.animations) {
for (let keyframe of animationInfo.keyframes) {