Skip to content

Commit

Permalink
Animated: Hoist Common Logic to Parent Class
Browse files Browse the repository at this point in the history
Summary:
Reduces redundancy and cleans up the layers of abstraction between the `Animation` class and its subclasses, by hoisting common and duplicated logic into the parent class.

I also cleaned up the Flow types in these files while I was at it.

Changelog:
[Internal]

Differential Revision: D63572064
  • Loading branch information
yungsters authored and facebook-github-bot committed Sep 28, 2024
1 parent 8a9ac58 commit e28edea
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 149 deletions.
46 changes: 37 additions & 9 deletions packages/react-native/Libraries/Animated/animations/Animation.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @flow strict-local
* @format
*/

'use strict';

import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type AnimatedNode from '../nodes/AnimatedNode';
import type AnimatedValue from '../nodes/AnimatedValue';
Expand All @@ -21,14 +19,15 @@ import AnimatedProps from '../nodes/AnimatedProps';
export type EndResult = {finished: boolean, value?: number, ...};
export type EndCallback = (result: EndResult) => void;

export type AnimationConfig = {
export type AnimationConfig = $ReadOnly<{
isInteraction?: boolean,
useNativeDriver: boolean,
platformConfig?: PlatformConfig,
onComplete?: ?EndCallback,
iterations?: number,
isLooping?: boolean,
};
...
}>;

let startNativeAnimationNextId = 1;

Expand All @@ -38,11 +37,21 @@ let startNativeAnimationNextId = 1;
export default class Animation {
#nativeID: ?number;
#onEnd: ?EndCallback;
#useNativeDriver: boolean;

__active: boolean;
__isInteraction: boolean;
__iterations: number;
__isLooping: ?boolean;
__iterations: number;

constructor(config: AnimationConfig) {
this.#useNativeDriver = NativeAnimatedHelper.shouldUseNativeDriver(config);

this.__active = false;
this.__isInteraction = config.isInteraction ?? !this.#useNativeDriver;
this.__isLooping = config.isLooping;
this.__iterations = config.iterations ?? 1;
}

start(
fromValue: number,
Expand All @@ -51,16 +60,29 @@ export default class Animation {
previousAnimation: ?Animation,
animatedValue: AnimatedValue,
): void {
if (!this.#useNativeDriver && animatedValue.__isNative === true) {
throw new Error(
'Attempting to run JS driven animation on animated node ' +
'that has been moved to "native" earlier by starting an ' +
'animation with `useNativeDriver: true`',
);
}

this.#onEnd = onEnd;
this.__active = true;
}

stop(): void {
if (this.#nativeID != null) {
NativeAnimatedHelper.API.stopAnimation(this.#nativeID);
}
this.__active = false;
}

__getNativeAnimationConfig(): any {
__getNativeAnimationConfig(): $ReadOnly<{
platformConfig: ?PlatformConfig,
...
}> {
// Subclasses that have corresponding animation implementation done in native
// should override this method
throw new Error('This animation type cannot be offloaded to native');
Expand Down Expand Up @@ -88,7 +110,11 @@ export default class Animation {
return result;
}

__startNativeAnimation(animatedValue: AnimatedValue): void {
__startNativeAnimation(animatedValue: AnimatedValue): boolean {
if (!this.#useNativeDriver) {
return false;
}

const startNativeAnimationWaitId = `${startNativeAnimationNextId}:startAnimation`;
startNativeAnimationNextId += 1;
NativeAnimatedHelper.API.setWaitingForIdentifier(
Expand All @@ -114,7 +140,7 @@ export default class Animation {

if (
ReactNativeFeatureFlags.shouldSkipStateUpdatesForLoopingAnimations() &&
this.__isLooping
this.__isLooping === true
) {
return;
}
Expand All @@ -127,6 +153,8 @@ export default class Animation {
}
},
);

return true;
} catch (e) {
throw e;
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,35 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @flow strict-local
* @format
*/

'use strict';

import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type AnimatedValue from '../nodes/AnimatedValue';
import type {AnimationConfig, EndCallback} from './Animation';

import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper';
import Animation from './Animation';

export type DecayAnimationConfig = {
export type DecayAnimationConfig = $ReadOnly<{
...AnimationConfig,
velocity:
| number
| {
| $ReadOnly<{
x: number,
y: number,
...
},
}>,
deceleration?: number,
};
...
}>;

export type DecayAnimationConfigSingle = {
export type DecayAnimationConfigSingle = $ReadOnly<{
...AnimationConfig,
velocity: number,
deceleration?: number,
};
...
}>;

export default class DecayAnimation extends Animation {
_startTime: number;
Expand All @@ -42,27 +41,25 @@ export default class DecayAnimation extends Animation {
_deceleration: number;
_velocity: number;
_onUpdate: (value: number) => void;
_animationFrame: any;
_useNativeDriver: boolean;
_animationFrame: ?AnimationFrameID;
_platformConfig: ?PlatformConfig;

constructor(config: DecayAnimationConfigSingle) {
super();
super(config);

this._deceleration = config.deceleration ?? 0.998;
this._velocity = config.velocity;
this._useNativeDriver = NativeAnimatedHelper.shouldUseNativeDriver(config);
this._platformConfig = config.platformConfig;
this.__isInteraction = config.isInteraction ?? !this._useNativeDriver;
this.__iterations = config.iterations ?? 1;
}

__getNativeAnimationConfig(): {|
__getNativeAnimationConfig(): $ReadOnly<{
deceleration: number,
iterations: number,
platformConfig: ?PlatformConfig,
type: $TEMPORARY$string<'decay'>,
type: 'decay',
velocity: number,
|} {
...
}> {
return {
type: 'decay',
deceleration: this._deceleration,
Expand All @@ -81,25 +78,14 @@ export default class DecayAnimation extends Animation {
): void {
super.start(fromValue, onUpdate, onEnd, previousAnimation, animatedValue);

if (!this._useNativeDriver && animatedValue.__isNative === true) {
throw new Error(
'Attempting to run JS driven animation on animated node ' +
'that has been moved to "native" earlier by starting an ' +
'animation with `useNativeDriver: true`',
);
}

this.__active = true;
this._lastValue = fromValue;
this._fromValue = fromValue;
this._onUpdate = onUpdate;
this._startTime = Date.now();

if (this._useNativeDriver) {
this.__startNativeAnimation(animatedValue);
} else {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
const useNativeDriver = this.__startNativeAnimation(animatedValue);
if (!useNativeDriver) {
this._animationFrame = requestAnimationFrame(() => this.onUpdate());
}
}

Expand Down Expand Up @@ -127,8 +113,9 @@ export default class DecayAnimation extends Animation {

stop(): void {
super.stop();
this.__active = false;
global.cancelAnimationFrame(this._animationFrame);
if (this._animationFrame != null) {
global.cancelAnimationFrame(this._animationFrame);
}
this.__debouncedOnEnd({finished: false});
}
}
Loading

0 comments on commit e28edea

Please sign in to comment.