Skip to content

Commit

Permalink
fix(core): animation iteration correct for android css animations and…
Browse files Browse the repository at this point in the history
… iOS rotation (#9628)

closes #7712

Co-authored-by: Nathan Walker <walkerrunpdx@gmail.com>
  • Loading branch information
CatchABus and NathanWalker committed Mar 1, 2022
1 parent 9b5d125 commit 608bb1e
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 171 deletions.
2 changes: 1 addition & 1 deletion apps/automated/src/ui/animation/css-animation-tests.ts
Expand Up @@ -73,7 +73,7 @@ export function test_ReadIterations() {
let animation = createAnimationFromCSS('.test { animation-iteration-count: 5; }', 'test');
TKUnit.assertEqual(animation.iterations, 5);
animation = createAnimationFromCSS('.test { animation-iteration-count: infinite; }', 'test');
TKUnit.assertEqual(animation.iterations, Number.MAX_VALUE);
TKUnit.assertEqual(animation.iterations, Number.POSITIVE_INFINITY);
}

export function test_ReadFillMode() {
Expand Down
281 changes: 115 additions & 166 deletions packages/core/ui/animation/index.android.ts
Expand Up @@ -27,16 +27,6 @@ const easeInOut = lazy(() => new android.view.animation.AccelerateDecelerateInte
const linear = lazy(() => new android.view.animation.LinearInterpolator());
const bounce = lazy(() => new android.view.animation.BounceInterpolator());

const keyPrefix = 'ui.animation.';
const propertyKeys = {};
propertyKeys[Properties.backgroundColor] = Symbol(keyPrefix + Properties.backgroundColor);
propertyKeys[Properties.opacity] = Symbol(keyPrefix + Properties.opacity);
propertyKeys[Properties.rotate] = Symbol(keyPrefix + Properties.rotate);
propertyKeys[Properties.scale] = Symbol(keyPrefix + Properties.scale);
propertyKeys[Properties.translate] = Symbol(keyPrefix + Properties.translate);
propertyKeys[Properties.height] = Symbol(keyPrefix + Properties.height);
propertyKeys[Properties.width] = Symbol(keyPrefix + Properties.width);

export function _resolveAnimationCurve(curve: string | CubicBezierAnimationCurve | android.view.animation.Interpolator | android.view.animation.LinearInterpolator): android.view.animation.Interpolator {
switch (curve) {
case 'easeIn':
Expand Down Expand Up @@ -178,27 +168,27 @@ export class Animation extends AnimationBase {
return this._rejectAlreadyPlaying();
}

if (this._animatorSet) {
return this._play();
}

this._animators = new Array<android.animation.Animator>();
this._propertyUpdateCallbacks = new Array<Function>();
this._propertyResetCallbacks = new Array<Function>();
const animationFinishedPromise = super.play();

for (let i = 0, length = this._propertyAnimations.length; i < length; i++) {
this._createAnimators(this._propertyAnimations[i]);
}
if (!this._animatorSet) {
this._animators = new Array<android.animation.Animator>();
this._propertyUpdateCallbacks = new Array<Function>();
this._propertyResetCallbacks = new Array<Function>();

this._nativeAnimatorsArray = Array.create(android.animation.Animator, this._animators.length);
for (let i = 0, length = this._animators.length; i < length; i++) {
this._nativeAnimatorsArray[i] = this._animators[i];
}
for (let i = 0, length = this._propertyAnimations.length; i < length; i++) {
this._createAnimators(this._propertyAnimations[i]);
}

this._animatorSet = new android.animation.AnimatorSet();
this._animatorSet.addListener(this._animatorListener);
this._nativeAnimatorsArray = Array.create(android.animation.Animator, this._animators.length);
for (let i = 0, length = this._animators.length; i < length; i++) {
this._nativeAnimatorsArray[i] = this._animators[i];
}

return this._play();
this._animatorSet = new android.animation.AnimatorSet();
this._animatorSet.addListener(this._animatorListener);
}
this._play();
return animationFinishedPromise;
}

public cancel(): void {
Expand All @@ -217,9 +207,7 @@ export class Animation extends AnimationBase {
return _resolveAnimationCurve(curve);
}

private _play(): AnimationPromise {
const animationFinishedPromise = super.play();

private _play(): void {
if (Device.sdkVersion <= '23') {
this._animatorSet = new android.animation.AnimatorSet();
this._animatorSet.addListener(this._animatorListener);
Expand All @@ -239,8 +227,6 @@ export class Animation extends AnimationBase {

this._animatorSet.setupStartValues();
this._animatorSet.start();

return animationFinishedPromise;
}

private _onAndroidAnimationEnd() {
Expand Down Expand Up @@ -300,43 +286,26 @@ export class Animation extends AnimationBase {
let originalValue3;
const density = layout.getDisplayDensity();

const key = propertyKeys[propertyAnimation.property];
if (key) {
propertyAnimation.target[key] = propertyAnimation;
}
function checkAnimation(cb) {
return () => {
if (propertyAnimation.target[key] === propertyAnimation) {
delete propertyAnimation.target[key];
cb();
}
};
}

const setLocal = this._valueSource === 'animation';
const style = propertyAnimation.target.style;
switch (propertyAnimation.property) {
case Properties.opacity:
opacityProperty._initDefaultNativeValue(style);

originalValue1 = nativeView.getAlpha();
propertyUpdateCallbacks.push(
checkAnimation(() => {
propertyAnimation.target.style[setLocal ? opacityProperty.name : opacityProperty.keyframe] = propertyAnimation.value;
})
);
propertyResetCallbacks.push(
checkAnimation(() => {
if (setLocal) {
propertyAnimation.target.style[opacityProperty.name] = originalValue1;
} else {
propertyAnimation.target.style[opacityProperty.keyframe] = originalValue1;
}
if (propertyAnimation.target.nativeViewProtected) {
propertyAnimation.target[opacityProperty.setNative](propertyAnimation.target.style.opacity);
}
})
);
propertyUpdateCallbacks.push(() => {
propertyAnimation.target.style[setLocal ? opacityProperty.name : opacityProperty.keyframe] = propertyAnimation.value;
});
propertyResetCallbacks.push(() => {
if (setLocal) {
propertyAnimation.target.style[opacityProperty.name] = originalValue1;
} else {
propertyAnimation.target.style[opacityProperty.keyframe] = originalValue1;
}
if (propertyAnimation.target.nativeViewProtected) {
propertyAnimation.target[opacityProperty.setNative](propertyAnimation.target.style.opacity);
}
});
animators.push(createObjectAnimator(nativeView, 'alpha', propertyAnimation.value));
break;

Expand All @@ -358,24 +327,20 @@ export class Animation extends AnimationBase {
})
);

propertyUpdateCallbacks.push(
checkAnimation(() => {
propertyAnimation.target.style[setLocal ? backgroundColorProperty.name : backgroundColorProperty.keyframe] = propertyAnimation.value;
})
);
propertyResetCallbacks.push(
checkAnimation(() => {
if (setLocal) {
propertyAnimation.target.style[backgroundColorProperty.name] = originalValue1;
} else {
propertyAnimation.target.style[backgroundColorProperty.keyframe] = originalValue1;
}

if (propertyAnimation.target.nativeViewProtected && propertyAnimation.target[backgroundColorProperty.setNative]) {
propertyAnimation.target[backgroundColorProperty.setNative](propertyAnimation.target.style.backgroundColor);
}
})
);
propertyUpdateCallbacks.push(() => {
propertyAnimation.target.style[setLocal ? backgroundColorProperty.name : backgroundColorProperty.keyframe] = propertyAnimation.value;
});
propertyResetCallbacks.push(() => {
if (setLocal) {
propertyAnimation.target.style[backgroundColorProperty.name] = originalValue1;
} else {
propertyAnimation.target.style[backgroundColorProperty.keyframe] = originalValue1;
}

if (propertyAnimation.target.nativeViewProtected && propertyAnimation.target[backgroundColorProperty.setNative]) {
propertyAnimation.target[backgroundColorProperty.setNative](propertyAnimation.target.style.backgroundColor);
}
});
animators.push(animator);
break;
}
Expand All @@ -386,29 +351,25 @@ export class Animation extends AnimationBase {
originalValue1 = nativeView.getTranslationX() / density;
originalValue2 = nativeView.getTranslationY() / density;

propertyUpdateCallbacks.push(
checkAnimation(() => {
propertyAnimation.target.style[setLocal ? translateXProperty.name : translateXProperty.keyframe] = propertyAnimation.value.x;
propertyAnimation.target.style[setLocal ? translateYProperty.name : translateYProperty.keyframe] = propertyAnimation.value.y;
})
);

propertyResetCallbacks.push(
checkAnimation(() => {
if (setLocal) {
propertyAnimation.target.style[translateXProperty.name] = originalValue1;
propertyAnimation.target.style[translateYProperty.name] = originalValue2;
} else {
propertyAnimation.target.style[translateXProperty.keyframe] = originalValue1;
propertyAnimation.target.style[translateYProperty.keyframe] = originalValue2;
}

if (propertyAnimation.target.nativeViewProtected) {
propertyAnimation.target[translateXProperty.setNative](propertyAnimation.target.style.translateX);
propertyAnimation.target[translateYProperty.setNative](propertyAnimation.target.style.translateY);
}
})
);
propertyUpdateCallbacks.push(() => {
propertyAnimation.target.style[setLocal ? translateXProperty.name : translateXProperty.keyframe] = propertyAnimation.value.x;
propertyAnimation.target.style[setLocal ? translateYProperty.name : translateYProperty.keyframe] = propertyAnimation.value.y;
});

propertyResetCallbacks.push(() => {
if (setLocal) {
propertyAnimation.target.style[translateXProperty.name] = originalValue1;
propertyAnimation.target.style[translateYProperty.name] = originalValue2;
} else {
propertyAnimation.target.style[translateXProperty.keyframe] = originalValue1;
propertyAnimation.target.style[translateYProperty.keyframe] = originalValue2;
}

if (propertyAnimation.target.nativeViewProtected) {
propertyAnimation.target[translateXProperty.setNative](propertyAnimation.target.style.translateX);
propertyAnimation.target[translateYProperty.setNative](propertyAnimation.target.style.translateY);
}
});

animators.push(createAnimationSet([createObjectAnimator(nativeView, 'translationX', propertyAnimation.value.x * density), createObjectAnimator(nativeView, 'translationY', propertyAnimation.value.y * density)], propertyAnimation.iterations));
break;
Expand All @@ -420,29 +381,25 @@ export class Animation extends AnimationBase {
originalValue1 = nativeView.getScaleX();
originalValue2 = nativeView.getScaleY();

propertyUpdateCallbacks.push(
checkAnimation(() => {
propertyAnimation.target.style[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = propertyAnimation.value.x;
propertyAnimation.target.style[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = propertyAnimation.value.y;
})
);

propertyResetCallbacks.push(
checkAnimation(() => {
if (setLocal) {
propertyAnimation.target.style[scaleXProperty.name] = originalValue1;
propertyAnimation.target.style[scaleYProperty.name] = originalValue2;
} else {
propertyAnimation.target.style[scaleXProperty.keyframe] = originalValue1;
propertyAnimation.target.style[scaleYProperty.keyframe] = originalValue2;
}

if (propertyAnimation.target.nativeViewProtected) {
propertyAnimation.target[scaleXProperty.setNative](propertyAnimation.target.style.scaleX);
propertyAnimation.target[scaleYProperty.setNative](propertyAnimation.target.style.scaleY);
}
})
);
propertyUpdateCallbacks.push(() => {
propertyAnimation.target.style[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = propertyAnimation.value.x;
propertyAnimation.target.style[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = propertyAnimation.value.y;
});

propertyResetCallbacks.push(() => {
if (setLocal) {
propertyAnimation.target.style[scaleXProperty.name] = originalValue1;
propertyAnimation.target.style[scaleYProperty.name] = originalValue2;
} else {
propertyAnimation.target.style[scaleXProperty.keyframe] = originalValue1;
propertyAnimation.target.style[scaleYProperty.keyframe] = originalValue2;
}

if (propertyAnimation.target.nativeViewProtected) {
propertyAnimation.target[scaleXProperty.setNative](propertyAnimation.target.style.scaleX);
propertyAnimation.target[scaleYProperty.setNative](propertyAnimation.target.style.scaleY);
}
});

animators.push(createAnimationSet([createObjectAnimator(nativeView, 'scaleX', propertyAnimation.value.x), createObjectAnimator(nativeView, 'scaleY', propertyAnimation.value.y)], propertyAnimation.iterations));
break;
Expand All @@ -456,32 +413,28 @@ export class Animation extends AnimationBase {
originalValue2 = nativeView.getRotationY();
originalValue3 = nativeView.getRotation();

propertyUpdateCallbacks.push(
checkAnimation(() => {
propertyAnimation.target.style[setLocal ? rotateXProperty.name : rotateXProperty.keyframe] = propertyAnimation.value.x;
propertyAnimation.target.style[setLocal ? rotateYProperty.name : rotateYProperty.keyframe] = propertyAnimation.value.y;
propertyAnimation.target.style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = propertyAnimation.value.z;
})
);
propertyResetCallbacks.push(
checkAnimation(() => {
if (setLocal) {
propertyAnimation.target.style[rotateXProperty.name] = originalValue1;
propertyAnimation.target.style[rotateYProperty.name] = originalValue2;
propertyAnimation.target.style[rotateProperty.name] = originalValue3;
} else {
propertyAnimation.target.style[rotateXProperty.keyframe] = originalValue1;
propertyAnimation.target.style[rotateYProperty.keyframe] = originalValue2;
propertyAnimation.target.style[rotateProperty.keyframe] = originalValue3;
}

if (propertyAnimation.target.nativeViewProtected) {
propertyAnimation.target[rotateProperty.setNative](propertyAnimation.target.style.rotate);
propertyAnimation.target[rotateXProperty.setNative](propertyAnimation.target.style.rotateX);
propertyAnimation.target[rotateYProperty.setNative](propertyAnimation.target.style.rotateY);
}
})
);
propertyUpdateCallbacks.push(() => {
propertyAnimation.target.style[setLocal ? rotateXProperty.name : rotateXProperty.keyframe] = propertyAnimation.value.x;
propertyAnimation.target.style[setLocal ? rotateYProperty.name : rotateYProperty.keyframe] = propertyAnimation.value.y;
propertyAnimation.target.style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = propertyAnimation.value.z;
});
propertyResetCallbacks.push(() => {
if (setLocal) {
propertyAnimation.target.style[rotateXProperty.name] = originalValue1;
propertyAnimation.target.style[rotateYProperty.name] = originalValue2;
propertyAnimation.target.style[rotateProperty.name] = originalValue3;
} else {
propertyAnimation.target.style[rotateXProperty.keyframe] = originalValue1;
propertyAnimation.target.style[rotateYProperty.keyframe] = originalValue2;
propertyAnimation.target.style[rotateProperty.keyframe] = originalValue3;
}

if (propertyAnimation.target.nativeViewProtected) {
propertyAnimation.target[rotateProperty.setNative](propertyAnimation.target.style.rotate);
propertyAnimation.target[rotateXProperty.setNative](propertyAnimation.target.style.rotateX);
propertyAnimation.target[rotateYProperty.setNative](propertyAnimation.target.style.rotateY);
}
});

animators.push(createAnimationSet([createObjectAnimator(nativeView, 'rotationX', propertyAnimation.value.x), createObjectAnimator(nativeView, 'rotationY', propertyAnimation.value.y), createObjectAnimator(nativeView, 'rotation', propertyAnimation.value.z)], propertyAnimation.iterations));
break;
Expand Down Expand Up @@ -514,20 +467,16 @@ export class Animation extends AnimationBase {
},
})
);
propertyUpdateCallbacks.push(
checkAnimation(() => {
propertyAnimation.target.style[targetStyle] = propertyAnimation.value;
})
);
propertyResetCallbacks.push(
checkAnimation(() => {
propertyAnimation.target.style[targetStyle] = originalValue1;
if (propertyAnimation.target.nativeViewProtected) {
const setter = propertyAnimation.target[extentProperty.setNative];
setter(propertyAnimation.target.style[propertyAnimation.property]);
}
})
);
propertyUpdateCallbacks.push(() => {
propertyAnimation.target.style[targetStyle] = propertyAnimation.value;
});
propertyResetCallbacks.push(() => {
propertyAnimation.target.style[targetStyle] = originalValue1;
if (propertyAnimation.target.nativeViewProtected) {
const setter = propertyAnimation.target[extentProperty.setNative];
setter(propertyAnimation.target.style[propertyAnimation.property]);
}
});
animators.push(extentAnimator);
break;
}
Expand Down
11 changes: 10 additions & 1 deletion packages/core/ui/animation/index.ios.ts
Expand Up @@ -464,10 +464,19 @@ export class Animation extends AnimationBase {
private static _createGroupAnimation(args: AnimationInfo, animation: PropertyAnimation) {
const groupAnimation = CAAnimationGroup.new();
groupAnimation.duration = args.duration;
if (args.repeatCount !== undefined) {
groupAnimation.repeatCount = args.repeatCount;
}
if (args.delay !== undefined) {
groupAnimation.beginTime = CACurrentMediaTime() + args.delay;
}
if (animation.curve !== undefined) {
groupAnimation.timingFunction = animation.curve;
}
const animations = NSMutableArray.alloc<CAAnimation>().initWithCapacity(3);

args.subPropertiesToAnimate.forEach((property) => {
const basicAnimationArgs = { ...args };
const basicAnimationArgs = { ...args, duration: undefined, repeatCount: undefined, delay: undefined, curve: undefined };
basicAnimationArgs.propertyNameToAnimate = `${args.propertyNameToAnimate}.${property}`;
basicAnimationArgs.fromValue = args.fromValue[property];
basicAnimationArgs.toValue = args.toValue[property];
Expand Down

0 comments on commit 608bb1e

Please sign in to comment.