251 lines
6.3 KiB
JavaScript
251 lines
6.3 KiB
JavaScript
|
/**
|
||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||
|
*
|
||
|
* This source code is licensed under the MIT license found in the
|
||
|
* LICENSE file in the root directory of this source tree.
|
||
|
*
|
||
|
* @flow
|
||
|
* @format
|
||
|
*/
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
import type {PlatformConfig} from '../AnimatedPlatformConfig';
|
||
|
|
||
|
import AnimatedValue from './AnimatedValue';
|
||
|
import AnimatedWithChildren from './AnimatedWithChildren';
|
||
|
import invariant from 'invariant';
|
||
|
|
||
|
export type AnimatedValueXYConfig = $ReadOnly<{
|
||
|
useNativeDriver: boolean,
|
||
|
}>;
|
||
|
type ValueXYListenerCallback = (value: {
|
||
|
x: number,
|
||
|
y: number,
|
||
|
...
|
||
|
}) => mixed;
|
||
|
|
||
|
let _uniqueId = 1;
|
||
|
|
||
|
/**
|
||
|
* 2D Value for driving 2D animations, such as pan gestures. Almost identical
|
||
|
* API to normal `Animated.Value`, but multiplexed.
|
||
|
*
|
||
|
* See https://reactnative.dev/docs/animatedvaluexy
|
||
|
*/
|
||
|
export default class AnimatedValueXY extends AnimatedWithChildren {
|
||
|
x: AnimatedValue;
|
||
|
y: AnimatedValue;
|
||
|
_listeners: {
|
||
|
[key: string]: {
|
||
|
x: string,
|
||
|
y: string,
|
||
|
...
|
||
|
},
|
||
|
...
|
||
|
};
|
||
|
|
||
|
constructor(
|
||
|
valueIn?: ?{
|
||
|
+x: number | AnimatedValue,
|
||
|
+y: number | AnimatedValue,
|
||
|
...
|
||
|
},
|
||
|
config?: ?AnimatedValueXYConfig,
|
||
|
) {
|
||
|
super();
|
||
|
const value: any = valueIn || {x: 0, y: 0}; // @flowfixme: shouldn't need `: any`
|
||
|
if (typeof value.x === 'number' && typeof value.y === 'number') {
|
||
|
this.x = new AnimatedValue(value.x);
|
||
|
this.y = new AnimatedValue(value.y);
|
||
|
} else {
|
||
|
invariant(
|
||
|
value.x instanceof AnimatedValue && value.y instanceof AnimatedValue,
|
||
|
'AnimatedValueXY must be initialized with an object of numbers or ' +
|
||
|
'AnimatedValues.',
|
||
|
);
|
||
|
this.x = value.x;
|
||
|
this.y = value.y;
|
||
|
}
|
||
|
this._listeners = {};
|
||
|
if (config && config.useNativeDriver) {
|
||
|
this.__makeNative();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Directly set the value. This will stop any animations running on the value
|
||
|
* and update all the bound properties.
|
||
|
*
|
||
|
* See https://reactnative.dev/docs/animatedvaluexy#setvalue
|
||
|
*/
|
||
|
setValue(value: {x: number, y: number, ...}) {
|
||
|
this.x.setValue(value.x);
|
||
|
this.y.setValue(value.y);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets an offset that is applied on top of whatever value is set, whether
|
||
|
* via `setValue`, an animation, or `Animated.event`. Useful for compensating
|
||
|
* things like the start of a pan gesture.
|
||
|
*
|
||
|
* See https://reactnative.dev/docs/animatedvaluexy#setoffset
|
||
|
*/
|
||
|
setOffset(offset: {x: number, y: number, ...}) {
|
||
|
this.x.setOffset(offset.x);
|
||
|
this.y.setOffset(offset.y);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Merges the offset value into the base value and resets the offset to zero.
|
||
|
* The final output of the value is unchanged.
|
||
|
*
|
||
|
* See https://reactnative.dev/docs/animatedvaluexy#flattenoffset
|
||
|
*/
|
||
|
flattenOffset(): void {
|
||
|
this.x.flattenOffset();
|
||
|
this.y.flattenOffset();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the offset value to the base value, and resets the base value to
|
||
|
* zero. The final output of the value is unchanged.
|
||
|
*
|
||
|
* See https://reactnative.dev/docs/animatedvaluexy#extractoffset
|
||
|
*/
|
||
|
extractOffset(): void {
|
||
|
this.x.extractOffset();
|
||
|
this.y.extractOffset();
|
||
|
}
|
||
|
|
||
|
__getValue(): {
|
||
|
x: number,
|
||
|
y: number,
|
||
|
...
|
||
|
} {
|
||
|
return {
|
||
|
x: this.x.__getValue(),
|
||
|
y: this.y.__getValue(),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Stops any animation and resets the value to its original.
|
||
|
*
|
||
|
* See https://reactnative.dev/docs/animatedvaluexy#resetanimation
|
||
|
*/
|
||
|
resetAnimation(
|
||
|
callback?: (value: {
|
||
|
x: number,
|
||
|
y: number,
|
||
|
...
|
||
|
}) => void,
|
||
|
): void {
|
||
|
this.x.resetAnimation();
|
||
|
this.y.resetAnimation();
|
||
|
callback && callback(this.__getValue());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Stops any running animation or tracking. `callback` is invoked with the
|
||
|
* final value after stopping the animation, which is useful for updating
|
||
|
* state to match the animation position with layout.
|
||
|
*
|
||
|
* See https://reactnative.dev/docs/animatedvaluexy#stopanimation
|
||
|
*/
|
||
|
stopAnimation(
|
||
|
callback?: (value: {
|
||
|
x: number,
|
||
|
y: number,
|
||
|
...
|
||
|
}) => void,
|
||
|
): void {
|
||
|
this.x.stopAnimation();
|
||
|
this.y.stopAnimation();
|
||
|
callback && callback(this.__getValue());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Adds an asynchronous listener to the value so you can observe updates from
|
||
|
* animations. This is useful because there is no way to synchronously read
|
||
|
* the value because it might be driven natively.
|
||
|
*
|
||
|
* Returns a string that serves as an identifier for the listener.
|
||
|
*
|
||
|
* See https://reactnative.dev/docs/animatedvaluexy#addlistener
|
||
|
*/
|
||
|
addListener(callback: ValueXYListenerCallback): string {
|
||
|
const id = String(_uniqueId++);
|
||
|
const jointCallback = ({value: number}: any) => {
|
||
|
callback(this.__getValue());
|
||
|
};
|
||
|
this._listeners[id] = {
|
||
|
x: this.x.addListener(jointCallback),
|
||
|
y: this.y.addListener(jointCallback),
|
||
|
};
|
||
|
return id;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Unregister a listener. The `id` param shall match the identifier
|
||
|
* previously returned by `addListener()`.
|
||
|
*
|
||
|
* See https://reactnative.dev/docs/animatedvaluexy#removelistener
|
||
|
*/
|
||
|
removeListener(id: string): void {
|
||
|
this.x.removeListener(this._listeners[id].x);
|
||
|
this.y.removeListener(this._listeners[id].y);
|
||
|
delete this._listeners[id];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Remove all registered listeners.
|
||
|
*
|
||
|
* See https://reactnative.dev/docs/animatedvaluexy#removealllisteners
|
||
|
*/
|
||
|
removeAllListeners(): void {
|
||
|
this.x.removeAllListeners();
|
||
|
this.y.removeAllListeners();
|
||
|
this._listeners = {};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Converts `{x, y}` into `{left, top}` for use in style.
|
||
|
*
|
||
|
* See https://reactnative.dev/docs/animatedvaluexy#getlayout
|
||
|
*/
|
||
|
getLayout(): {[key: string]: AnimatedValue, ...} {
|
||
|
return {
|
||
|
left: this.x,
|
||
|
top: this.y,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Converts `{x, y}` into a useable translation transform.
|
||
|
*
|
||
|
* See https://reactnative.dev/docs/animatedvaluexy#gettranslatetransform
|
||
|
*/
|
||
|
getTranslateTransform(): Array<{[key: string]: AnimatedValue, ...}> {
|
||
|
return [{translateX: this.x}, {translateY: this.y}];
|
||
|
}
|
||
|
|
||
|
__attach(): void {
|
||
|
this.x.__addChild(this);
|
||
|
this.y.__addChild(this);
|
||
|
super.__attach();
|
||
|
}
|
||
|
|
||
|
__detach(): void {
|
||
|
this.x.__removeChild(this);
|
||
|
this.y.__removeChild(this);
|
||
|
super.__detach();
|
||
|
}
|
||
|
|
||
|
__makeNative(platformConfig: ?PlatformConfig) {
|
||
|
this.x.__makeNative(platformConfig);
|
||
|
this.y.__makeNative(platformConfig);
|
||
|
super.__makeNative(platformConfig);
|
||
|
}
|
||
|
}
|