Add AnimatedDiffClamp node

Summary:
This adds a new type of node that clamps an animated value between 2 values with a special twist, it is based on the difference between the previous value so getting far from a bound doesn't matter and as soon as we start getting closer again the value will start changing. The main use case for this node is to create a collapsible navbar when scrolling a scrollview. This is a pretty in apps (fb, youtube, twitter, all use something like this).

It updates using the following: `value = clamp(value + diff, min, max)` where `diff` is the difference with the previous value.

This gives the following output for parameters min = 0, max = 30:
```
  in     out
  0      0
  15     15
  30     30
  100    30
  90     20
  30     0
  50     20
```

One issue I see is that this node is pretty specific to this use case but I can't see another simple way to do this with Animated that can also be offloaded to native easily. I'd be glad to discuss other solutions if some
Closes https://github.com/facebook/react-native/pull/9419

Differential Revision: D3753920

fbshipit-source-id: 40a749d38fd003aab2d3cb5cb8f0535e467d8a2a
This commit is contained in:
Janic Duplessis 2016-08-23 13:59:07 -07:00 committed by Facebook Github Bot 4
parent 777a9c0a0e
commit cd1d652af4
2 changed files with 69 additions and 0 deletions

View File

@ -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<Object>;
@ -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.
*/

View File

@ -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]);
}
});
});