Permalink
Browse files

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 #9419

Differential Revision: D3753920

fbshipit-source-id: 40a749d38fd003aab2d3cb5cb8f0535e467d8a2a
  • Loading branch information...
1 parent 777a9c0 commit cd1d652af4ebed123dbbee1d015ba3835ec33356 @janicduplessis janicduplessis committed with Facebook Github Bot 4 Aug 23, 2016
@@ -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) => {
@@ -2085,6 +2130,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.
*/
delay,
@@ -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]);
+ }
+ });
+});

0 comments on commit cd1d652

Please sign in to comment.