fix(core): animation iteration correct for android css animations and iOS rotation (#9628)

closes #7712

Co-authored-by: Nathan Walker <walkerrunpdx@gmail.com>
This commit is contained in:
Dimitris - Rafail Katsampas
2022-01-23 19:49:19 +02:00
committed by Nathan Walker
parent 9b5d125f42
commit 608bb1ed24
5 changed files with 125 additions and 168 deletions

View File

@@ -73,7 +73,7 @@ export function test_ReadIterations() {
let animation = createAnimationFromCSS('.test { animation-iteration-count: 5; }', 'test'); let animation = createAnimationFromCSS('.test { animation-iteration-count: 5; }', 'test');
TKUnit.assertEqual(animation.iterations, 5); TKUnit.assertEqual(animation.iterations, 5);
animation = createAnimationFromCSS('.test { animation-iteration-count: infinite; }', 'test'); 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() { export function test_ReadFillMode() {

View File

@@ -27,16 +27,6 @@ const easeInOut = lazy(() => new android.view.animation.AccelerateDecelerateInte
const linear = lazy(() => new android.view.animation.LinearInterpolator()); const linear = lazy(() => new android.view.animation.LinearInterpolator());
const bounce = lazy(() => new android.view.animation.BounceInterpolator()); 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 { export function _resolveAnimationCurve(curve: string | CubicBezierAnimationCurve | android.view.animation.Interpolator | android.view.animation.LinearInterpolator): android.view.animation.Interpolator {
switch (curve) { switch (curve) {
case 'easeIn': case 'easeIn':
@@ -178,27 +168,27 @@ export class Animation extends AnimationBase {
return this._rejectAlreadyPlaying(); return this._rejectAlreadyPlaying();
} }
if (this._animatorSet) { const animationFinishedPromise = super.play();
return this._play();
if (!this._animatorSet) {
this._animators = new Array<android.animation.Animator>();
this._propertyUpdateCallbacks = new Array<Function>();
this._propertyResetCallbacks = new Array<Function>();
for (let i = 0, length = this._propertyAnimations.length; i < length; i++) {
this._createAnimators(this._propertyAnimations[i]);
}
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];
}
this._animatorSet = new android.animation.AnimatorSet();
this._animatorSet.addListener(this._animatorListener);
} }
this._play();
this._animators = new Array<android.animation.Animator>(); return animationFinishedPromise;
this._propertyUpdateCallbacks = new Array<Function>();
this._propertyResetCallbacks = new Array<Function>();
for (let i = 0, length = this._propertyAnimations.length; i < length; i++) {
this._createAnimators(this._propertyAnimations[i]);
}
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];
}
this._animatorSet = new android.animation.AnimatorSet();
this._animatorSet.addListener(this._animatorListener);
return this._play();
} }
public cancel(): void { public cancel(): void {
@@ -217,9 +207,7 @@ export class Animation extends AnimationBase {
return _resolveAnimationCurve(curve); return _resolveAnimationCurve(curve);
} }
private _play(): AnimationPromise { private _play(): void {
const animationFinishedPromise = super.play();
if (Device.sdkVersion <= '23') { if (Device.sdkVersion <= '23') {
this._animatorSet = new android.animation.AnimatorSet(); this._animatorSet = new android.animation.AnimatorSet();
this._animatorSet.addListener(this._animatorListener); this._animatorSet.addListener(this._animatorListener);
@@ -239,8 +227,6 @@ export class Animation extends AnimationBase {
this._animatorSet.setupStartValues(); this._animatorSet.setupStartValues();
this._animatorSet.start(); this._animatorSet.start();
return animationFinishedPromise;
} }
private _onAndroidAnimationEnd() { private _onAndroidAnimationEnd() {
@@ -300,19 +286,6 @@ export class Animation extends AnimationBase {
let originalValue3; let originalValue3;
const density = layout.getDisplayDensity(); 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 setLocal = this._valueSource === 'animation';
const style = propertyAnimation.target.style; const style = propertyAnimation.target.style;
switch (propertyAnimation.property) { switch (propertyAnimation.property) {
@@ -320,23 +293,19 @@ export class Animation extends AnimationBase {
opacityProperty._initDefaultNativeValue(style); opacityProperty._initDefaultNativeValue(style);
originalValue1 = nativeView.getAlpha(); originalValue1 = nativeView.getAlpha();
propertyUpdateCallbacks.push( propertyUpdateCallbacks.push(() => {
checkAnimation(() => { propertyAnimation.target.style[setLocal ? opacityProperty.name : opacityProperty.keyframe] = propertyAnimation.value;
propertyAnimation.target.style[setLocal ? opacityProperty.name : opacityProperty.keyframe] = propertyAnimation.value; });
}) propertyResetCallbacks.push(() => {
); if (setLocal) {
propertyResetCallbacks.push( propertyAnimation.target.style[opacityProperty.name] = originalValue1;
checkAnimation(() => { } else {
if (setLocal) { propertyAnimation.target.style[opacityProperty.keyframe] = originalValue1;
propertyAnimation.target.style[opacityProperty.name] = originalValue1; }
} else { if (propertyAnimation.target.nativeViewProtected) {
propertyAnimation.target.style[opacityProperty.keyframe] = originalValue1; propertyAnimation.target[opacityProperty.setNative](propertyAnimation.target.style.opacity);
} }
if (propertyAnimation.target.nativeViewProtected) { });
propertyAnimation.target[opacityProperty.setNative](propertyAnimation.target.style.opacity);
}
})
);
animators.push(createObjectAnimator(nativeView, 'alpha', propertyAnimation.value)); animators.push(createObjectAnimator(nativeView, 'alpha', propertyAnimation.value));
break; break;
@@ -358,24 +327,20 @@ export class Animation extends AnimationBase {
}) })
); );
propertyUpdateCallbacks.push( propertyUpdateCallbacks.push(() => {
checkAnimation(() => { propertyAnimation.target.style[setLocal ? backgroundColorProperty.name : backgroundColorProperty.keyframe] = propertyAnimation.value;
propertyAnimation.target.style[setLocal ? backgroundColorProperty.name : backgroundColorProperty.keyframe] = propertyAnimation.value; });
}) propertyResetCallbacks.push(() => {
); if (setLocal) {
propertyResetCallbacks.push( propertyAnimation.target.style[backgroundColorProperty.name] = originalValue1;
checkAnimation(() => { } else {
if (setLocal) { propertyAnimation.target.style[backgroundColorProperty.keyframe] = originalValue1;
propertyAnimation.target.style[backgroundColorProperty.name] = originalValue1; }
} else {
propertyAnimation.target.style[backgroundColorProperty.keyframe] = originalValue1;
}
if (propertyAnimation.target.nativeViewProtected && propertyAnimation.target[backgroundColorProperty.setNative]) { if (propertyAnimation.target.nativeViewProtected && propertyAnimation.target[backgroundColorProperty.setNative]) {
propertyAnimation.target[backgroundColorProperty.setNative](propertyAnimation.target.style.backgroundColor); propertyAnimation.target[backgroundColorProperty.setNative](propertyAnimation.target.style.backgroundColor);
} }
}) });
);
animators.push(animator); animators.push(animator);
break; break;
} }
@@ -386,29 +351,25 @@ export class Animation extends AnimationBase {
originalValue1 = nativeView.getTranslationX() / density; originalValue1 = nativeView.getTranslationX() / density;
originalValue2 = nativeView.getTranslationY() / density; originalValue2 = nativeView.getTranslationY() / density;
propertyUpdateCallbacks.push( propertyUpdateCallbacks.push(() => {
checkAnimation(() => { propertyAnimation.target.style[setLocal ? translateXProperty.name : translateXProperty.keyframe] = propertyAnimation.value.x;
propertyAnimation.target.style[setLocal ? translateXProperty.name : translateXProperty.keyframe] = propertyAnimation.value.x; propertyAnimation.target.style[setLocal ? translateYProperty.name : translateYProperty.keyframe] = propertyAnimation.value.y;
propertyAnimation.target.style[setLocal ? translateYProperty.name : translateYProperty.keyframe] = propertyAnimation.value.y; });
})
);
propertyResetCallbacks.push( propertyResetCallbacks.push(() => {
checkAnimation(() => { if (setLocal) {
if (setLocal) { propertyAnimation.target.style[translateXProperty.name] = originalValue1;
propertyAnimation.target.style[translateXProperty.name] = originalValue1; propertyAnimation.target.style[translateYProperty.name] = originalValue2;
propertyAnimation.target.style[translateYProperty.name] = originalValue2; } else {
} else { propertyAnimation.target.style[translateXProperty.keyframe] = originalValue1;
propertyAnimation.target.style[translateXProperty.keyframe] = originalValue1; propertyAnimation.target.style[translateYProperty.keyframe] = originalValue2;
propertyAnimation.target.style[translateYProperty.keyframe] = originalValue2; }
}
if (propertyAnimation.target.nativeViewProtected) { if (propertyAnimation.target.nativeViewProtected) {
propertyAnimation.target[translateXProperty.setNative](propertyAnimation.target.style.translateX); propertyAnimation.target[translateXProperty.setNative](propertyAnimation.target.style.translateX);
propertyAnimation.target[translateYProperty.setNative](propertyAnimation.target.style.translateY); 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)); animators.push(createAnimationSet([createObjectAnimator(nativeView, 'translationX', propertyAnimation.value.x * density), createObjectAnimator(nativeView, 'translationY', propertyAnimation.value.y * density)], propertyAnimation.iterations));
break; break;
@@ -420,29 +381,25 @@ export class Animation extends AnimationBase {
originalValue1 = nativeView.getScaleX(); originalValue1 = nativeView.getScaleX();
originalValue2 = nativeView.getScaleY(); originalValue2 = nativeView.getScaleY();
propertyUpdateCallbacks.push( propertyUpdateCallbacks.push(() => {
checkAnimation(() => { propertyAnimation.target.style[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = propertyAnimation.value.x;
propertyAnimation.target.style[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = propertyAnimation.value.x; propertyAnimation.target.style[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = propertyAnimation.value.y;
propertyAnimation.target.style[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = propertyAnimation.value.y; });
})
);
propertyResetCallbacks.push( propertyResetCallbacks.push(() => {
checkAnimation(() => { if (setLocal) {
if (setLocal) { propertyAnimation.target.style[scaleXProperty.name] = originalValue1;
propertyAnimation.target.style[scaleXProperty.name] = originalValue1; propertyAnimation.target.style[scaleYProperty.name] = originalValue2;
propertyAnimation.target.style[scaleYProperty.name] = originalValue2; } else {
} else { propertyAnimation.target.style[scaleXProperty.keyframe] = originalValue1;
propertyAnimation.target.style[scaleXProperty.keyframe] = originalValue1; propertyAnimation.target.style[scaleYProperty.keyframe] = originalValue2;
propertyAnimation.target.style[scaleYProperty.keyframe] = originalValue2; }
}
if (propertyAnimation.target.nativeViewProtected) { if (propertyAnimation.target.nativeViewProtected) {
propertyAnimation.target[scaleXProperty.setNative](propertyAnimation.target.style.scaleX); propertyAnimation.target[scaleXProperty.setNative](propertyAnimation.target.style.scaleX);
propertyAnimation.target[scaleYProperty.setNative](propertyAnimation.target.style.scaleY); 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)); animators.push(createAnimationSet([createObjectAnimator(nativeView, 'scaleX', propertyAnimation.value.x), createObjectAnimator(nativeView, 'scaleY', propertyAnimation.value.y)], propertyAnimation.iterations));
break; break;
@@ -456,32 +413,28 @@ export class Animation extends AnimationBase {
originalValue2 = nativeView.getRotationY(); originalValue2 = nativeView.getRotationY();
originalValue3 = nativeView.getRotation(); originalValue3 = nativeView.getRotation();
propertyUpdateCallbacks.push( propertyUpdateCallbacks.push(() => {
checkAnimation(() => { propertyAnimation.target.style[setLocal ? rotateXProperty.name : rotateXProperty.keyframe] = propertyAnimation.value.x;
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 ? rotateYProperty.name : rotateYProperty.keyframe] = propertyAnimation.value.y; propertyAnimation.target.style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = propertyAnimation.value.z;
propertyAnimation.target.style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = propertyAnimation.value.z; });
}) propertyResetCallbacks.push(() => {
); if (setLocal) {
propertyResetCallbacks.push( propertyAnimation.target.style[rotateXProperty.name] = originalValue1;
checkAnimation(() => { propertyAnimation.target.style[rotateYProperty.name] = originalValue2;
if (setLocal) { propertyAnimation.target.style[rotateProperty.name] = originalValue3;
propertyAnimation.target.style[rotateXProperty.name] = originalValue1; } else {
propertyAnimation.target.style[rotateYProperty.name] = originalValue2; propertyAnimation.target.style[rotateXProperty.keyframe] = originalValue1;
propertyAnimation.target.style[rotateProperty.name] = originalValue3; propertyAnimation.target.style[rotateYProperty.keyframe] = originalValue2;
} else { propertyAnimation.target.style[rotateProperty.keyframe] = originalValue3;
propertyAnimation.target.style[rotateXProperty.keyframe] = originalValue1; }
propertyAnimation.target.style[rotateYProperty.keyframe] = originalValue2;
propertyAnimation.target.style[rotateProperty.keyframe] = originalValue3;
}
if (propertyAnimation.target.nativeViewProtected) { if (propertyAnimation.target.nativeViewProtected) {
propertyAnimation.target[rotateProperty.setNative](propertyAnimation.target.style.rotate); propertyAnimation.target[rotateProperty.setNative](propertyAnimation.target.style.rotate);
propertyAnimation.target[rotateXProperty.setNative](propertyAnimation.target.style.rotateX); propertyAnimation.target[rotateXProperty.setNative](propertyAnimation.target.style.rotateX);
propertyAnimation.target[rotateYProperty.setNative](propertyAnimation.target.style.rotateY); 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)); animators.push(createAnimationSet([createObjectAnimator(nativeView, 'rotationX', propertyAnimation.value.x), createObjectAnimator(nativeView, 'rotationY', propertyAnimation.value.y), createObjectAnimator(nativeView, 'rotation', propertyAnimation.value.z)], propertyAnimation.iterations));
break; break;
@@ -514,20 +467,16 @@ export class Animation extends AnimationBase {
}, },
}) })
); );
propertyUpdateCallbacks.push( propertyUpdateCallbacks.push(() => {
checkAnimation(() => { propertyAnimation.target.style[targetStyle] = propertyAnimation.value;
propertyAnimation.target.style[targetStyle] = propertyAnimation.value; });
}) propertyResetCallbacks.push(() => {
); propertyAnimation.target.style[targetStyle] = originalValue1;
propertyResetCallbacks.push( if (propertyAnimation.target.nativeViewProtected) {
checkAnimation(() => { const setter = propertyAnimation.target[extentProperty.setNative];
propertyAnimation.target.style[targetStyle] = originalValue1; setter(propertyAnimation.target.style[propertyAnimation.property]);
if (propertyAnimation.target.nativeViewProtected) { }
const setter = propertyAnimation.target[extentProperty.setNative]; });
setter(propertyAnimation.target.style[propertyAnimation.property]);
}
})
);
animators.push(extentAnimator); animators.push(extentAnimator);
break; break;
} }

View File

@@ -464,10 +464,19 @@ export class Animation extends AnimationBase {
private static _createGroupAnimation(args: AnimationInfo, animation: PropertyAnimation) { private static _createGroupAnimation(args: AnimationInfo, animation: PropertyAnimation) {
const groupAnimation = CAAnimationGroup.new(); const groupAnimation = CAAnimationGroup.new();
groupAnimation.duration = args.duration; 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); const animations = NSMutableArray.alloc<CAAnimation>().initWithCapacity(3);
args.subPropertiesToAnimate.forEach((property) => { args.subPropertiesToAnimate.forEach((property) => {
const basicAnimationArgs = { ...args }; const basicAnimationArgs = { ...args, duration: undefined, repeatCount: undefined, delay: undefined, curve: undefined };
basicAnimationArgs.propertyNameToAnimate = `${args.propertyNameToAnimate}.${property}`; basicAnimationArgs.propertyNameToAnimate = `${args.propertyNameToAnimate}.${property}`;
basicAnimationArgs.fromValue = args.fromValue[property]; basicAnimationArgs.fromValue = args.fromValue[property];
basicAnimationArgs.toValue = args.toValue[property]; basicAnimationArgs.toValue = args.toValue[property];

View File

@@ -228,8 +228,7 @@ export class KeyframeAnimation {
if (cachedAnimation) { if (cachedAnimation) {
animation = cachedAnimation; animation = cachedAnimation;
} else { } else {
const animationDef = this.animations[index]; const animationDef = { ...this.animations[index], target: view };
(<any>animationDef).target = view;
animation = new Animation([animationDef]); animation = new Animation([animationDef]);
this._nativeAnimations.push(animation); this._nativeAnimations.push(animation);
} }

View File

@@ -10,7 +10,7 @@ const ANIMATION_PROPERTY_HANDLERS = Object.freeze({
'animation-duration': (info: any, value: any) => (info.duration = timeConverter(value)), 'animation-duration': (info: any, value: any) => (info.duration = timeConverter(value)),
'animation-delay': (info: any, value: any) => (info.delay = timeConverter(value)), 'animation-delay': (info: any, value: any) => (info.delay = timeConverter(value)),
'animation-timing-function': (info: any, value: any) => (info.curve = animationTimingFunctionConverter(value)), 'animation-timing-function': (info: any, value: any) => (info.curve = animationTimingFunctionConverter(value)),
'animation-iteration-count': (info: any, value: any) => (info.iterations = value === 'infinite' ? Number.MAX_VALUE : parseFloat(value)), 'animation-iteration-count': (info: any, value: any) => (info.iterations = value === 'infinite' ? Number.POSITIVE_INFINITY : parseFloat(value)),
'animation-direction': (info: any, value: any) => (info.isReverse = value === 'reverse'), 'animation-direction': (info: any, value: any) => (info.isReverse = value === 'reverse'),
'animation-fill-mode': (info: any, value: any) => (info.isForwards = value === 'forwards'), 'animation-fill-mode': (info: any, value: any) => (info.isForwards = value === 'forwards'),
}); });