`isInteraction` option to keep InteractionManager unblocked
Summary: Adds a new option to animation configs so that animations can be marked as non-interactions. This solves the "loading indicator problem" where an animated loading screen would deadlock as it waits for all interactions to complete. As for the API, what we really want long term is better scheduling. This needs to be built at the runtime level (into the RN bridge) and take care of things like priority donation that are probably overkill right now. Basically this API is a medium-term fix and I won't be upset if it gets replaced with a finer-grained scheduler. Closes https://github.com/facebook/react-native/pull/3433 Reviewed By: @svcscm Differential Revision: D2545300 Pulled By: @vjeux fb-gh-sync-id: c1216bf69bfbbbae00c76c4697183fe96a6a01dd
This commit is contained in:
parent
1e52b8297c
commit
c38d7fab97
|
@ -40,11 +40,16 @@ class Animated {
|
|||
__getChildren(): Array<Animated> { return []; }
|
||||
}
|
||||
|
||||
type AnimationConfig = {
|
||||
isInteraction?: bool;
|
||||
};
|
||||
|
||||
// Important note: start() and stop() will only be called at most once.
|
||||
// Once an animation has been stopped or finished its course, it will
|
||||
// not be reused.
|
||||
class Animation {
|
||||
__active: bool;
|
||||
__isInteraction: bool;
|
||||
__onEnd: ?EndCallback;
|
||||
start(
|
||||
fromValue: number,
|
||||
|
@ -128,14 +133,14 @@ function _flush(rootNode: AnimatedValue): void {
|
|||
animatedStyles.forEach(animatedStyle => animatedStyle.update());
|
||||
}
|
||||
|
||||
type TimingAnimationConfig = {
|
||||
type TimingAnimationConfig = AnimationConfig & {
|
||||
toValue: number | AnimatedValue | {x: number, y: number} | AnimatedValueXY;
|
||||
easing?: (value: number) => number;
|
||||
duration?: number;
|
||||
delay?: number;
|
||||
};
|
||||
|
||||
type TimingAnimationConfigSingle = {
|
||||
type TimingAnimationConfigSingle = AnimationConfig & {
|
||||
toValue: number | AnimatedValue;
|
||||
easing?: (value: number) => number;
|
||||
duration?: number;
|
||||
|
@ -163,6 +168,7 @@ class TimingAnimation extends Animation {
|
|||
this._easing = config.easing || easeInOut;
|
||||
this._duration = config.duration !== undefined ? config.duration : 500;
|
||||
this._delay = config.delay || 0;
|
||||
this.__isInteraction = config.isInteraction !== undefined ? config.isInteraction : true;
|
||||
}
|
||||
|
||||
start(
|
||||
|
@ -223,12 +229,12 @@ class TimingAnimation extends Animation {
|
|||
}
|
||||
}
|
||||
|
||||
type DecayAnimationConfig = {
|
||||
type DecayAnimationConfig = AnimationConfig & {
|
||||
velocity: number | {x: number, y: number};
|
||||
deceleration?: number;
|
||||
};
|
||||
|
||||
type DecayAnimationConfigSingle = {
|
||||
type DecayAnimationConfigSingle = AnimationConfig & {
|
||||
velocity: number;
|
||||
deceleration?: number;
|
||||
};
|
||||
|
@ -248,6 +254,7 @@ class DecayAnimation extends Animation {
|
|||
super();
|
||||
this._deceleration = config.deceleration || 0.998;
|
||||
this._velocity = config.velocity;
|
||||
this.__isInteraction = config.isInteraction !== undefined ? config.isInteraction : true;
|
||||
}
|
||||
|
||||
start(
|
||||
|
@ -291,7 +298,7 @@ class DecayAnimation extends Animation {
|
|||
}
|
||||
}
|
||||
|
||||
type SpringAnimationConfig = {
|
||||
type SpringAnimationConfig = AnimationConfig & {
|
||||
toValue: number | AnimatedValue | {x: number, y: number} | AnimatedValueXY;
|
||||
overshootClamping?: bool;
|
||||
restDisplacementThreshold?: number;
|
||||
|
@ -303,7 +310,7 @@ type SpringAnimationConfig = {
|
|||
friction?: number;
|
||||
};
|
||||
|
||||
type SpringAnimationConfigSingle = {
|
||||
type SpringAnimationConfigSingle = AnimationConfig & {
|
||||
toValue: number | AnimatedValue;
|
||||
overshootClamping?: bool;
|
||||
restDisplacementThreshold?: number;
|
||||
|
@ -349,6 +356,7 @@ class SpringAnimation extends Animation {
|
|||
this._initialVelocity = config.velocity;
|
||||
this._lastVelocity = withDefault(config.velocity, 0);
|
||||
this._toValue = config.toValue;
|
||||
this.__isInteraction = config.isInteraction !== undefined ? config.isInteraction : true;
|
||||
|
||||
var springConfig;
|
||||
if (config.bounciness !== undefined || config.speed !== undefined) {
|
||||
|
@ -611,7 +619,10 @@ class AnimatedValue extends AnimatedWithChildren {
|
|||
* class.
|
||||
*/
|
||||
animate(animation: Animation, callback: ?EndCallback): void {
|
||||
var handle = InteractionManager.createInteractionHandle();
|
||||
var handle = null;
|
||||
if (animation.__isInteraction) {
|
||||
handle = InteractionManager.createInteractionHandle();
|
||||
}
|
||||
var previousAnimation = this._animation;
|
||||
this._animation && this._animation.stop();
|
||||
this._animation = animation;
|
||||
|
@ -622,7 +633,9 @@ class AnimatedValue extends AnimatedWithChildren {
|
|||
},
|
||||
(result) => {
|
||||
this._animation = null;
|
||||
InteractionManager.clearInteractionHandle(handle);
|
||||
if (handle !== null) {
|
||||
InteractionManager.clearInteractionHandle(handle);
|
||||
}
|
||||
callback && callback(result);
|
||||
},
|
||||
previousAnimation,
|
||||
|
|
|
@ -18,6 +18,7 @@ jest
|
|||
var Animated = require('Animated');
|
||||
|
||||
describe('Animated', () => {
|
||||
|
||||
it('works end to end', () => {
|
||||
var anim = new Animated.Value(0);
|
||||
|
||||
|
@ -352,6 +353,54 @@ describe('Animated Events', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Animated Interactions', () => {
|
||||
/*eslint-disable no-shadow*/
|
||||
var Animated;
|
||||
/*eslint-enable*/
|
||||
var InteractionManager;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.mock('InteractionManager');
|
||||
Animated = require('Animated');
|
||||
InteractionManager = require('InteractionManager');
|
||||
});
|
||||
|
||||
afterEach(()=> {
|
||||
jest.dontMock('InteractionManager');
|
||||
});
|
||||
|
||||
it('registers an interaction by default', () => {
|
||||
InteractionManager.createInteractionHandle.mockReturnValue(777);
|
||||
|
||||
var value = new Animated.Value(0);
|
||||
var callback = jest.genMockFunction();
|
||||
Animated.timing(value, {
|
||||
toValue: 100,
|
||||
duration: 100,
|
||||
}).start(callback);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(InteractionManager.createInteractionHandle).toBeCalled();
|
||||
expect(InteractionManager.clearInteractionHandle).toBeCalledWith(777);
|
||||
expect(callback).toBeCalledWith({finished: true});
|
||||
});
|
||||
|
||||
it('does not register an interaction when specified', () => {
|
||||
var value = new Animated.Value(0);
|
||||
var callback = jest.genMockFunction();
|
||||
Animated.timing(value, {
|
||||
toValue: 100,
|
||||
duration: 100,
|
||||
isInteraction: false,
|
||||
}).start(callback);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(InteractionManager.createInteractionHandle).not.toBeCalled();
|
||||
expect(InteractionManager.clearInteractionHandle).not.toBeCalled();
|
||||
expect(callback).toBeCalledWith({finished: true});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Animated Tracking', () => {
|
||||
it('should track values', () => {
|
||||
var value1 = new Animated.Value(0);
|
||||
|
|
Loading…
Reference in New Issue