diff --git a/Libraries/Animated/src/AnimatedImplementation.js b/Libraries/Animated/src/AnimatedImplementation.js index 05b7ea05fc240b..dfc404e83bb15b 100644 --- a/Libraries/Animated/src/AnimatedImplementation.js +++ b/Libraries/Animated/src/AnimatedImplementation.js @@ -1193,6 +1193,43 @@ class AnimatedModulo extends AnimatedWithChildren { } } +class AnimatedDiffClamp extends AnimatedWithChildren { + _a: Animated; + _min: number; + _max: number; + _value: number; + _lastValue: number; + + constructor(a: Animated, min: number, max: number) { + super(); + + this._a = a; + this._min = min; + this._max = max; + this._value = this._lastValue = this._a.__getValue(); + } + + interpolate(config: InterpolationConfigType): AnimatedInterpolation { + return new AnimatedInterpolation(this, config); + } + + __getValue(): number { + const value = this._a.__getValue(); + const diff = value - this._lastValue; + this._lastValue = value; + this._value = Math.min(Math.max(this._value + diff, this._min), this._max); + return this._value; + } + + __attach(): void { + this._a.__addChild(this); + } + + __detach(): void { + this._a.__removeChild(this); + } +} + class AnimatedTransform extends AnimatedWithChildren { _transforms: Array; @@ -1693,6 +1730,14 @@ var modulo = function( return new AnimatedModulo(a, modulus); }; +var diffClamp = function( + a: Animated, + min: number, + max: number, +): AnimatedDiffClamp { + return new AnimatedDiffClamp(a, min, max); +}; + const _combineCallbacks = function(callback: ?EndCallback, config : AnimationConfig) { if (callback && config.onComplete) { return (...args) => { @@ -2084,6 +2129,17 @@ module.exports = { */ modulo, + /** + * Create a new Animated value that is limited between 2 values. It uses the + * difference between the last value so even if the value is far from the bounds + * it will start changing when the value starts getting closer again. + * (`value = clamp(value + diff, min, max)`). + * + * This is useful with scroll events, for example, to show the navbar when + * scrolling up and to hide it when scrolling down. + */ + diffClamp, + /** * Starts an animation after the given delay. */ diff --git a/Libraries/Animated/src/__tests__/Animated-test.js b/Libraries/Animated/src/__tests__/Animated-test.js index 02cb505755dab4..d57f9b672448cb 100644 --- a/Libraries/Animated/src/__tests__/Animated-test.js +++ b/Libraries/Animated/src/__tests__/Animated-test.js @@ -567,3 +567,16 @@ describe('Animated Listeners', () => { expect(listener.mock.calls.length).toBe(4); }); }); + +describe('Animated Diff Clamp', () => { + it('should get the proper value', () => { + const inputValues = [0, 20, 40, 30, 0, -40, -10, -20, 0]; + const expectedValues = [0, 20, 20, 10, 0, 0, 20, 10, 20]; + const value = new Animated.Value(0); + const diffClampValue = Animated.diffClamp(value, 0, 20); + for (let i = 0; i < inputValues.length; i++) { + value.setValue(inputValues[i]); + expect(diffClampValue.__getValue()).toBe(expectedValues[i]); + } + }); +});