From e3507b3010583d8063d9c98094d0f732fa9a94a4 Mon Sep 17 00:00:00 2001 From: Dylan Vann Date: Sat, 26 May 2018 01:07:16 -0400 Subject: [PATCH 1/8] Add interpolate function from kmagiera with example. https://github.com/kmagiera/react-native-reanimated/issues/10#issuecomment-392066106 --- Example/.gitignore | 3 + Example/App.js | 2 + Example/interpolate/index.js | 131 +++++++++++++++++++++++++++++++++++ Example/package.json | 5 +- Example/rn-cli.config.js | 3 + Example/yarn.lock | 10 +++ src/derived.js | 27 +++++++- 7 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 Example/interpolate/index.js create mode 100644 Example/rn-cli.config.js diff --git a/Example/.gitignore b/Example/.gitignore index 5d647565fa9e..555ded8afadf 100644 --- a/Example/.gitignore +++ b/Example/.gitignore @@ -54,3 +54,6 @@ buck-out/ # Bundle artifact *.jsbundle + +# metro-with-symlinks +metro.config.js diff --git a/Example/App.js b/Example/App.js index 2356be36b13f..1e8b4f58abaf 100644 --- a/Example/App.js +++ b/Example/App.js @@ -6,6 +6,7 @@ import { RectButton, ScrollView } from 'react-native-gesture-handler'; import Snappable from './snappable'; import ImageViewer from './imageViewer'; import Test from './test'; +import Interpolate from './interpolate' YellowBox.ignoreWarnings([ 'Warning: isMounted(...) is deprecated', @@ -18,6 +19,7 @@ const SCREENS = { Snappable: { screen: Snappable, title: 'Snappable' }, Test: { screen: Test, title: 'Test' }, ImageViewer: { screen: ImageViewer, title: 'Image Viewer' }, + Interpolate: { screen: Interpolate, title: 'Interpolate' }, }; class MainScreen extends React.Component { diff --git a/Example/interpolate/index.js b/Example/interpolate/index.js new file mode 100644 index 000000000000..3cb5cf0a1f91 --- /dev/null +++ b/Example/interpolate/index.js @@ -0,0 +1,131 @@ +import React, { Component } from 'react'; +import { StyleSheet, View } from 'react-native'; + +import Animated, { Easing } from 'react-native-reanimated'; + +const { + set, + cond, + eq, + and, + add, + call, + multiply, + lessThan, + startClock, + stopClock, + clockRunning, + block, + timing, + debug, + spring, + Value, + Clock, + event, + interpolate, +} = Animated; + +function runSpring(clock, value, dest) { + const state = { + finished: new Value(0), + velocity: new Value(0), + position: new Value(0), + time: new Value(0), + }; + + const config = { + toValue: new Value(0), + damping: 7, + mass: 1, + stiffness: 121.6, + overshootClamping: false, + restSpeedThreshold: 0.001, + restDisplacementThreshold: 0.001, + }; + + return block([ + cond(clockRunning(clock), 0, [ + set(state.finished, 0), + set(state.time, 0), + set(state.position, value), + set(state.velocity, -2500), + set(config.toValue, dest), + startClock(clock), + ]), + spring(clock, state, config), + cond(state.finished, debug('stop clock', stopClock(clock))), + state.position, + ]); +} + +function runTiming(clock, value, dest) { + const state = { + finished: new Value(1), + position: new Value(value), + time: new Value(0), + frameTime: new Value(0), + }; + + const config = { + duration: 500, + toValue: new Value(0), + easing: Easing.inOut(Easing.ease), + }; + + const reset = [ + set(state.finished, 0), + set(state.time, 0), + set(state.frameTime, 0), + ] + + return block([ + cond(and(state.finished, eq(state.position, value)), [ + ...reset, + set(config.toValue, dest), + ]), + cond(and(state.finished, eq(state.position, dest)), [ + ...reset, + set(config.toValue, value), + ]), + cond(clockRunning(clock), 0, startClock(clock)), + timing(clock, state, config), + state.position, + ]); +} + +export default class Example extends Component { + constructor(props) { + super(props); + const clock = new Clock(); + const base = runTiming(clock, -120, 120); + this._transX = interpolate(base, [-120, -60, 60, 120], [-20, -1, 1, 20]) + } + render() { + return ( + + + + ); + } +} + +const BOX_SIZE = 100; + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#F5FCFF', + }, + box: { + width: BOX_SIZE, + height: BOX_SIZE, + borderColor: '#F5FCFF', + alignSelf: 'center', + backgroundColor: 'plum', + margin: BOX_SIZE / 2, + }, +}); diff --git a/Example/package.json b/Example/package.json index 3200cc30f0dd..f3f1e4084eff 100644 --- a/Example/package.json +++ b/Example/package.json @@ -3,9 +3,9 @@ "version": "0.0.1", "private": true, "scripts": { - "start": "node node_modules/react-native/local-cli/cli.js start", + "start": "metro-with-symlinks start", "test": "jest", - "postinstall": "rm -rf node_modules/react-native-reanimated/{.git,node_modules,Example}" + "postinstall": "metro-with-symlinks" }, "dependencies": { "react": "16.3.2", @@ -18,6 +18,7 @@ "babel-jest": "22.4.3", "babel-preset-react-native": "4.0.0", "jest": "22.4.3", + "metro-with-symlinks": "^1.0.12", "react-test-renderer": "16.3.1" }, "jest": { diff --git a/Example/rn-cli.config.js b/Example/rn-cli.config.js new file mode 100644 index 000000000000..0f540cbc902d --- /dev/null +++ b/Example/rn-cli.config.js @@ -0,0 +1,3 @@ +const config = require('./metro.config.js') + +module.exports = config diff --git a/Example/yarn.lock b/Example/yarn.lock index 275e0f02a679..f6593d56f71c 100644 --- a/Example/yarn.lock +++ b/Example/yarn.lock @@ -1715,6 +1715,10 @@ decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" +dedent-js@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dedent-js/-/dedent-js-1.0.1.tgz#bee5fb7c9e727d85dffa24590d10ec1ab1255305" + deep-extend@~0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" @@ -3408,6 +3412,12 @@ metro-source-map@0.30.2: dependencies: source-map "^0.5.6" +metro-with-symlinks@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/metro-with-symlinks/-/metro-with-symlinks-1.0.12.tgz#d76599face0e6f6dfc849de042249427809ee48c" + dependencies: + dedent-js "^1.0.1" + metro@^0.30.0: version "0.30.2" resolved "https://registry.yarnpkg.com/metro/-/metro-0.30.2.tgz#e722e0eb106530f6d5bcf8de1f50353a0732cfb3" diff --git a/src/derived.js b/src/derived.js index 6981bf27076c..4e31b0318d88 100644 --- a/src/derived.js +++ b/src/derived.js @@ -7,11 +7,12 @@ import { sub, set, add, + divide, } from './base'; import AnimatedValue from './core/AnimatedValue'; import { adapt } from './utils'; -export function abs(a) { +export const abs = function(a) { return cond(lessThan(a, 0), multiply(-1, a), a); } @@ -49,3 +50,27 @@ export const diffClamp = function(a, minVal, maxVal) { min(max(add(cond(defined(value), value, a), diff(a)), minVal), maxVal) ); }; + +const interpolateInternalSingle = function(value, inputRange, outputRange, offset) { + const inS = inputRange[offset]; + const inE = inputRange[offset + 1]; + const outS = outputRange[offset]; + const outE = outputRange[offset + 1]; + const progress = divide(sub(value, inS), sub(inE, inS)); + return add(outS, multiply(progress, sub(outE, outS))); +} + +const interpolateInternal = function(value, inputRange, outputRange, offset = 0) { + if (inputRange.length - offset === 2) { + return interpolateSingle(value, inputRange, outputRange, offset); + } + return cond( + lessThan(value, inputRange[offset + 1]), + interpolateSingle(value, inputRange, outputRange, offset), + interpolate(value, inputRange, outputRange, offset + 1) + ); +} + +export const interpolate = function(value, inputRange, outputRange) { + return interpolateInternal(value, inputRange, outputRange) +} From 3a2b557d85037390214285adacffadbfd0f07a72 Mon Sep 17 00:00:00 2001 From: Dylan Vann Date: Sat, 26 May 2018 01:10:27 -0400 Subject: [PATCH 2/8] Finish renaming. --- src/derived.js | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/derived.js b/src/derived.js index 4e31b0318d88..bc3a4ed86cf7 100644 --- a/src/derived.js +++ b/src/derived.js @@ -14,7 +14,7 @@ import { adapt } from './utils'; export const abs = function(a) { return cond(lessThan(a, 0), multiply(-1, a), a); -} +}; export const min = function(a, b) { a = adapt(a); @@ -51,26 +51,36 @@ export const diffClamp = function(a, minVal, maxVal) { ); }; -const interpolateInternalSingle = function(value, inputRange, outputRange, offset) { +const interpolateInternalSingle = function( + value, + inputRange, + outputRange, + offset +) { const inS = inputRange[offset]; const inE = inputRange[offset + 1]; const outS = outputRange[offset]; const outE = outputRange[offset + 1]; const progress = divide(sub(value, inS), sub(inE, inS)); return add(outS, multiply(progress, sub(outE, outS))); -} +}; -const interpolateInternal = function(value, inputRange, outputRange, offset = 0) { +const interpolateInternal = function( + value, + inputRange, + outputRange, + offset = 0 +) { if (inputRange.length - offset === 2) { - return interpolateSingle(value, inputRange, outputRange, offset); + return interpolateInternalSingle(value, inputRange, outputRange, offset); } return cond( lessThan(value, inputRange[offset + 1]), - interpolateSingle(value, inputRange, outputRange, offset), - interpolate(value, inputRange, outputRange, offset + 1) + interpolateInternalSingle(value, inputRange, outputRange, offset), + interpolateInternal(value, inputRange, outputRange, offset + 1) ); -} +}; export const interpolate = function(value, inputRange, outputRange) { - return interpolateInternal(value, inputRange, outputRange) -} + return interpolateInternal(value, inputRange, outputRange); +}; From f4ab3f86de661af72393247488b3ea8a3756258f Mon Sep 17 00:00:00 2001 From: Dylan Vann Date: Sun, 27 May 2018 22:18:20 -0400 Subject: [PATCH 3/8] Add extrapolation handling, add more examples, add titles. --- Example/App.js | 8 +- Example/imageViewer/index.js | 3 + Example/interpolate/index.js | 131 ------------ Example/package.json | 2 +- Example/snappable/index.js | 3 + Example/src/Box.js | 25 +++ Example/src/Row.js | 14 ++ Example/src/interpolate/AnimatedBounds.js | 233 ++++++++++++++++++++++ Example/src/interpolate/Basic.js | 81 ++++++++ Example/src/interpolate/WithDrag.js | 176 ++++++++++++++++ Example/src/interpolate/index.js | 69 +++++++ Example/yarn.lock | 69 +++++-- src/derived.js | 39 +++- 13 files changed, 701 insertions(+), 152 deletions(-) delete mode 100644 Example/interpolate/index.js create mode 100644 Example/src/Box.js create mode 100644 Example/src/Row.js create mode 100644 Example/src/interpolate/AnimatedBounds.js create mode 100644 Example/src/interpolate/Basic.js create mode 100644 Example/src/interpolate/WithDrag.js create mode 100644 Example/src/interpolate/index.js diff --git a/Example/App.js b/Example/App.js index 1e8b4f58abaf..57669322e09b 100644 --- a/Example/App.js +++ b/Example/App.js @@ -1,12 +1,12 @@ import React from 'react'; import { Text, View, FlatList, StyleSheet, YellowBox } from 'react-native'; -import { StackNavigator } from 'react-navigation'; +import { createStackNavigator } from 'react-navigation'; import { RectButton, ScrollView } from 'react-native-gesture-handler'; import Snappable from './snappable'; import ImageViewer from './imageViewer'; import Test from './test'; -import Interpolate from './interpolate' +import Interpolate from './src/interpolate'; YellowBox.ignoreWarnings([ 'Warning: isMounted(...) is deprecated', @@ -24,7 +24,7 @@ const SCREENS = { class MainScreen extends React.Component { static navigationOptions = { - title: '🎬 Reanimated Demo', + title: '🎬 Reanimated Examples', }; render() { const data = Object.keys(SCREENS).map(key => ({ key })); @@ -59,7 +59,7 @@ class MainScreenItem extends React.Component { } } -const ExampleApp = StackNavigator( +const ExampleApp = createStackNavigator( { Main: { screen: MainScreen }, ...SCREENS, diff --git a/Example/imageViewer/index.js b/Example/imageViewer/index.js index 78ec369e1fc0..84a6c8671420 100644 --- a/Example/imageViewer/index.js +++ b/Example/imageViewer/index.js @@ -420,6 +420,9 @@ class Viewer extends Component { } export default class Example extends Component { + static navigationOptions = { + title: 'Image Viewer Example', + }; render() { return ( diff --git a/Example/interpolate/index.js b/Example/interpolate/index.js deleted file mode 100644 index 3cb5cf0a1f91..000000000000 --- a/Example/interpolate/index.js +++ /dev/null @@ -1,131 +0,0 @@ -import React, { Component } from 'react'; -import { StyleSheet, View } from 'react-native'; - -import Animated, { Easing } from 'react-native-reanimated'; - -const { - set, - cond, - eq, - and, - add, - call, - multiply, - lessThan, - startClock, - stopClock, - clockRunning, - block, - timing, - debug, - spring, - Value, - Clock, - event, - interpolate, -} = Animated; - -function runSpring(clock, value, dest) { - const state = { - finished: new Value(0), - velocity: new Value(0), - position: new Value(0), - time: new Value(0), - }; - - const config = { - toValue: new Value(0), - damping: 7, - mass: 1, - stiffness: 121.6, - overshootClamping: false, - restSpeedThreshold: 0.001, - restDisplacementThreshold: 0.001, - }; - - return block([ - cond(clockRunning(clock), 0, [ - set(state.finished, 0), - set(state.time, 0), - set(state.position, value), - set(state.velocity, -2500), - set(config.toValue, dest), - startClock(clock), - ]), - spring(clock, state, config), - cond(state.finished, debug('stop clock', stopClock(clock))), - state.position, - ]); -} - -function runTiming(clock, value, dest) { - const state = { - finished: new Value(1), - position: new Value(value), - time: new Value(0), - frameTime: new Value(0), - }; - - const config = { - duration: 500, - toValue: new Value(0), - easing: Easing.inOut(Easing.ease), - }; - - const reset = [ - set(state.finished, 0), - set(state.time, 0), - set(state.frameTime, 0), - ] - - return block([ - cond(and(state.finished, eq(state.position, value)), [ - ...reset, - set(config.toValue, dest), - ]), - cond(and(state.finished, eq(state.position, dest)), [ - ...reset, - set(config.toValue, value), - ]), - cond(clockRunning(clock), 0, startClock(clock)), - timing(clock, state, config), - state.position, - ]); -} - -export default class Example extends Component { - constructor(props) { - super(props); - const clock = new Clock(); - const base = runTiming(clock, -120, 120); - this._transX = interpolate(base, [-120, -60, 60, 120], [-20, -1, 1, 20]) - } - render() { - return ( - - - - ); - } -} - -const BOX_SIZE = 100; - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: '#F5FCFF', - }, - box: { - width: BOX_SIZE, - height: BOX_SIZE, - borderColor: '#F5FCFF', - alignSelf: 'center', - backgroundColor: 'plum', - margin: BOX_SIZE / 2, - }, -}); diff --git a/Example/package.json b/Example/package.json index f3f1e4084eff..a85ee08cd90b 100644 --- a/Example/package.json +++ b/Example/package.json @@ -12,7 +12,7 @@ "react-native": "0.55.3", "react-native-gesture-handler": "^1.0.0", "react-native-reanimated": "file:../", - "react-navigation": "^1.5.11" + "react-navigation": "^2.0.4" }, "devDependencies": { "babel-jest": "22.4.3", diff --git a/Example/snappable/index.js b/Example/snappable/index.js index e21d6d5f813c..481a0c8ed1a2 100644 --- a/Example/snappable/index.js +++ b/Example/snappable/index.js @@ -128,6 +128,9 @@ class Snappable extends Component { } export default class Example extends Component { + static navigationOptions = { + title: 'Snappable Example', + }; render() { return ( diff --git a/Example/src/Box.js b/Example/src/Box.js new file mode 100644 index 000000000000..479a66a9c85d --- /dev/null +++ b/Example/src/Box.js @@ -0,0 +1,25 @@ +import React from 'react'; +import { StyleSheet } from 'react-native'; +import Animated, { Easing } from 'react-native-reanimated'; + +/** + * Needs to be a class component for react-native-gesture-handler to put a ref on it. + */ +export default class Box extends React.Component { + render() { + const { style, ...props } = this.props; + return ; + } +} + +const BOX_SIZE = 44; + +const styles = StyleSheet.create({ + box: { + width: BOX_SIZE, + height: BOX_SIZE, + alignSelf: 'center', + backgroundColor: 'blue', + margin: 10, + }, +}); diff --git a/Example/src/Row.js b/Example/src/Row.js new file mode 100644 index 000000000000..33b7ca6f2a61 --- /dev/null +++ b/Example/src/Row.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { View, StyleSheet } from 'react-native'; + +const Row = ({ style, ...props }) => ( + +); + +const styles = StyleSheet.create({ + style: { + height: 64, + }, +}); + +export default Row; diff --git a/Example/src/interpolate/AnimatedBounds.js b/Example/src/interpolate/AnimatedBounds.js new file mode 100644 index 000000000000..3c429c827839 --- /dev/null +++ b/Example/src/interpolate/AnimatedBounds.js @@ -0,0 +1,233 @@ +import React, { Component } from 'react'; +import { StyleSheet, View } from 'react-native'; +import Animated, { Easing } from 'react-native-reanimated'; +import { PanGestureHandler, State } from 'react-native-gesture-handler'; +import Box from '../Box'; +import Row from '../Row'; + +const { + set, + cond, + sub, + eq, + and, + add, + call, + multiply, + lessThan, + startClock, + stopClock, + clockRunning, + block, + timing, + debug, + spring, + Value, + Clock, + event, + interpolate, + defined, +} = Animated; + +function runSpring(clock, value, velocity, dest) { + const state = { + finished: new Value(0), + velocity: new Value(0), + position: new Value(0), + time: new Value(0), + }; + + const config = { + damping: 7, + mass: 1, + stiffness: 121.6, + overshootClamping: false, + restSpeedThreshold: 0.001, + restDisplacementThreshold: 0.001, + toValue: new Value(0), + }; + + return [ + cond(clockRunning(clock), 0, [ + set(state.finished, 0), + set(state.velocity, velocity), + set(state.position, value), + set(config.toValue, dest), + startClock(clock), + ]), + cond(state.finished, stopClock(clock)), + state.position, + ]; +} + +function runTiming(clock, value, dest) { + const state = { + finished: new Value(1), + position: new Value(value), + time: new Value(0), + frameTime: new Value(0), + }; + + const config = { + duration: 500, + toValue: new Value(0), + easing: Easing.inOut(Easing.ease), + }; + + const reset = [ + set(state.finished, 0), + set(state.time, 0), + set(state.frameTime, 0), + ]; + + return block([ + cond(and(state.finished, eq(state.position, value)), [ + ...reset, + set(config.toValue, dest), + ]), + cond(and(state.finished, eq(state.position, dest)), [ + ...reset, + set(config.toValue, value), + ]), + cond(clockRunning(clock), 0, startClock(clock)), + timing(clock, state, config), + state.position, + ]); +} + +const getThing = (min, max) => { + const clock = new Clock(); + const state = { + finished: new Value(1), + position: new Value(min), + time: new Value(0), + frameTime: new Value(0), + }; + + const config = { + duration: 500, + toValue: new Value(0), + easing: Easing.inOut(Easing.ease), + }; + + const reset = [ + set(state.finished, 0), + set(state.time, 0), + set(state.frameTime, 0), + ]; + + return block([ + cond(and(state.finished, eq(state.position, min)), [ + ...reset, + set(config.toValue, max), + ]), + cond(and(state.finished, eq(state.position, max)), [ + ...reset, + set(config.toValue, min), + ]), + cond(clockRunning(clock), 0, startClock(clock)), + timing(clock, state, config), + state.position, + ]); +}; + +export default class AnimatedBounds extends Component { + constructor(props) { + super(props); + const TOSS_SEC = 0.2; + + const dragX = new Value(0); + const state = new Value(-1); + const dragVX = new Value(0); + const transX = new Value(); + const prevDragX = new Value(0); + const clock = new Clock(); + + this._onGestureEvent = event([ + { nativeEvent: { translationX: dragX, velocityX: dragVX, state: state } }, + ]); + + const snapPoint = cond( + lessThan(add(transX, multiply(TOSS_SEC, dragVX)), 0), + -100, + 100 + ); + + this._transX = cond( + eq(state, State.ACTIVE), + [ + stopClock(clock), + set(transX, add(transX, sub(dragX, prevDragX))), + set(prevDragX, dragX), + transX, + ], + [ + set(prevDragX, 0), + set( + transX, + cond(defined(transX), runSpring(clock, transX, dragVX, snapPoint), 0) + ), + ] + ); + + this._transX = interpolate(this._transX, { + inputRange: [-100, 100], + outputRange: [-100, 100], + extrapolate: 'clamp', + }); + + const min = getThing(-100, -50); + const max = getThing(100, 50); + this._transXA = interpolate(this._transX, { + inputRange: [-100, 100], + outputRange: [min, max], + extrapolate: 'clamp', + }); + this.min = min; + this.max = max; + } + render() { + return ( + + + + + + + + + + + + + + + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + line: { + position: 'absolute', + alignSelf: 'center', + backgroundColor: 'red', + height: 64, + width: 1, + }, +}); diff --git a/Example/src/interpolate/Basic.js b/Example/src/interpolate/Basic.js new file mode 100644 index 000000000000..3111fe2de300 --- /dev/null +++ b/Example/src/interpolate/Basic.js @@ -0,0 +1,81 @@ +import React, { Component } from 'react'; +import { StyleSheet, View } from 'react-native'; +import Animated, { Easing } from 'react-native-reanimated'; +import Box from '../Box'; +import Row from '../Row'; + +const { + set, + cond, + eq, + and, + add, + call, + multiply, + lessThan, + startClock, + stopClock, + clockRunning, + block, + timing, + debug, + spring, + Value, + Clock, + event, + interpolate, +} = Animated; + +function runTiming(clock, value, dest) { + const state = { + finished: new Value(1), + position: new Value(value), + time: new Value(0), + frameTime: new Value(0), + }; + + const config = { + duration: 500, + toValue: new Value(0), + easing: Easing.inOut(Easing.ease), + }; + + const reset = [ + set(state.finished, 0), + set(state.time, 0), + set(state.frameTime, 0), + ]; + + return block([ + cond(and(state.finished, eq(state.position, value)), [ + ...reset, + set(config.toValue, dest), + ]), + cond(and(state.finished, eq(state.position, dest)), [ + ...reset, + set(config.toValue, value), + ]), + cond(clockRunning(clock), 0, startClock(clock)), + timing(clock, state, config), + state.position, + ]); +} + +export default class Basic extends Component { + constructor(props) { + super(props); + const clock = new Clock(); + const base = runTiming(clock, -1, 1); + this._transX = interpolate(base, { + inputRange: [-1, 1], + outputRange: [-100, 100], + }); + } + render() { + return ( + + + + ); + } +} diff --git a/Example/src/interpolate/WithDrag.js b/Example/src/interpolate/WithDrag.js new file mode 100644 index 000000000000..ff8b4a2684d5 --- /dev/null +++ b/Example/src/interpolate/WithDrag.js @@ -0,0 +1,176 @@ +import React, { Component } from 'react'; +import { StyleSheet, View } from 'react-native'; +import Animated, { Easing } from 'react-native-reanimated'; +import { PanGestureHandler, State } from 'react-native-gesture-handler'; +import Box from '../Box'; +import Row from '../Row'; + +const { + set, + cond, + sub, + eq, + and, + add, + call, + multiply, + lessThan, + startClock, + stopClock, + clockRunning, + block, + timing, + debug, + spring, + Value, + Clock, + event, + interpolate, + defined, +} = Animated; + +function runSpring(clock, value, velocity, dest) { + const state = { + finished: new Value(0), + velocity: new Value(0), + position: new Value(0), + time: new Value(0), + }; + + const config = { + damping: 7, + mass: 1, + stiffness: 121.6, + overshootClamping: false, + restSpeedThreshold: 0.001, + restDisplacementThreshold: 0.001, + toValue: new Value(0), + }; + + return [ + cond(clockRunning(clock), 0, [ + set(state.finished, 0), + set(state.velocity, velocity), + set(state.position, value), + set(config.toValue, dest), + startClock(clock), + ]), + cond(state.finished, stopClock(clock)), + state.position, + ]; +} + +function runTiming(clock, value, dest) { + const state = { + finished: new Value(1), + position: new Value(value), + time: new Value(0), + frameTime: new Value(0), + }; + + const config = { + duration: 500, + toValue: new Value(0), + easing: Easing.inOut(Easing.ease), + }; + + const reset = [ + set(state.finished, 0), + set(state.time, 0), + set(state.frameTime, 0), + ]; + + return block([ + cond(and(state.finished, eq(state.position, value)), [ + ...reset, + set(config.toValue, dest), + ]), + cond(and(state.finished, eq(state.position, dest)), [ + ...reset, + set(config.toValue, value), + ]), + cond(clockRunning(clock), 0, startClock(clock)), + timing(clock, state, config), + state.position, + ]); +} + +export default class WithDrag extends Component { + constructor(props) { + super(props); + const TOSS_SEC = 0.2; + + const dragX = new Value(0); + const state = new Value(-1); + const dragVX = new Value(0); + const transX = new Value(); + const prevDragX = new Value(0); + const clock = new Clock(); + + this._onGestureEvent = event([ + { nativeEvent: { translationX: dragX, velocityX: dragVX, state: state } }, + ]); + + const snapPoint = cond( + lessThan(add(transX, multiply(TOSS_SEC, dragVX)), 0), + -100, + 100 + ); + + this._transX = cond( + eq(state, State.ACTIVE), + [ + stopClock(clock), + set(transX, add(transX, sub(dragX, prevDragX))), + set(prevDragX, dragX), + transX, + ], + [ + set(prevDragX, 0), + set( + transX, + cond(defined(transX), runSpring(clock, transX, dragVX, snapPoint), 0) + ), + ] + ); + + this._transXA = interpolate(this._transX, { + inputRange: [-120, 120], + outputRange: [-100, 100], + }); + this._transXB = interpolate(this._transX, { + inputRange: [-120, -60, 60, 120], + outputRange: [-60, -10, 10, 60], + }); + } + render() { + return ( + + + + + + + + + + + + + + ); + } +} + +const BOX_SIZE = 44; + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, +}); diff --git a/Example/src/interpolate/index.js b/Example/src/interpolate/index.js new file mode 100644 index 000000000000..de909e46dfea --- /dev/null +++ b/Example/src/interpolate/index.js @@ -0,0 +1,69 @@ +import React from 'react'; +import { Text, View, FlatList, StyleSheet, YellowBox } from 'react-native'; +import { StackNavigator } from 'react-navigation'; +import { RectButton, ScrollView } from 'react-native-gesture-handler'; + +import Basic from './Basic'; +import WithDrag from './WithDrag'; +import AnimatedBounds from './AnimatedBounds'; + +const examples = [Basic, WithDrag, AnimatedBounds].map(v => ({ + key: v.displayName, + title: v.displayName, + Component: v, +})); + +class Item extends React.Component { + render() { + const item = this.props.item; + const Comp = this.props.item.Component; + + return ( + + {item.title} + + + ); + } +} + +class MainScreen extends React.Component { + static navigationOptions = { + title: 'Interpolation Examples', + }; + renderItem = props => ; + render() { + return ( + + ); + } +} + +const ItemSeparator = () => ; + +const styles = StyleSheet.create({ + list: { + backgroundColor: '#EFEFF4', + }, + separator: { + height: 1, + backgroundColor: '#DBDBE0', + }, + buttonText: { + backgroundColor: 'transparent', + }, + button: { + flex: 1, + flexDirection: 'column', + alignItems: 'center', + backgroundColor: '#fff', + padding: 20, + }, +}); + +export default MainScreen; diff --git a/Example/yarn.lock b/Example/yarn.lock index f6593d56f71c..49cd3abcb5b5 100644 --- a/Example/yarn.lock +++ b/Example/yarn.lock @@ -1657,6 +1657,13 @@ create-react-class@^15.6.3: loose-envify "^1.3.1" object-assign "^4.1.1" +create-react-context@^0.2.1: + version "0.2.2" + resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.2.tgz#9836542f9aaa22868cd7d4a6f82667df38019dca" + dependencies: + fbjs "^0.8.0" + gud "^1.0.0" + cross-spawn@^5.0.1, cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -2093,7 +2100,7 @@ fbjs-scripts@^0.8.1: semver "^5.1.0" through2 "^2.0.0" -fbjs@^0.8.14, fbjs@^0.8.16, fbjs@^0.8.9: +fbjs@^0.8.0, fbjs@^0.8.14, fbjs@^0.8.16, fbjs@^0.8.9: version "0.8.16" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" dependencies: @@ -2327,6 +2334,10 @@ growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" +gud@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" + handlebars@^4.0.3: version "4.0.11" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" @@ -2412,7 +2423,7 @@ hoek@4.x.x: version "4.2.1" resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" -hoist-non-react-statics@^2.2.0, hoist-non-react-statics@^2.3.1: +hoist-non-react-statics@^2.2.0, hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz#d2ca2dfc19c5a91c5a6615ce8e564ef0347e2a40" @@ -4170,9 +4181,9 @@ react-is@^16.3.1: version "16.3.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.3.2.tgz#f4d3d0e2f5fbb6ac46450641eb2e25bf05d36b22" -react-lifecycles-compat@^1.0.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-1.1.4.tgz#fc005c72849b7ed364de20a0f64ff58ebdc2009a" +react-lifecycles-compat@^3, react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" react-native-dismiss-keyboard@1.0.0: version "1.0.0" @@ -4207,9 +4218,21 @@ react-native-safe-area-view@^0.7.0: dependencies: hoist-non-react-statics "^2.3.1" -"react-native-tab-view@github:react-navigation/react-native-tab-view": - version "0.0.74" - resolved "https://codeload.github.com/react-navigation/react-native-tab-view/tar.gz/36ebd834d78b841fc19778c966465d02fd1213bb" +react-native-safe-area-view@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/react-native-safe-area-view/-/react-native-safe-area-view-0.8.0.tgz#22d78cb8e8658d04a10cd53c1546e0bc86cb7aea" + dependencies: + hoist-non-react-statics "^2.3.1" + +react-native-tab-view@^0.0.77: + version "0.0.77" + resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-0.0.77.tgz#11ceb8e7c23100d07e628dc151b57797524d00d4" + dependencies: + prop-types "^15.6.0" + +react-native-tab-view@~0.0.78: + version "0.0.78" + resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-0.0.78.tgz#9b90730d89cbd34a03f0e0ab10e74ca7af945560" dependencies: prop-types "^15.6.0" @@ -4277,18 +4300,36 @@ react-native@0.55.3: xmldoc "^0.4.0" yargs "^9.0.0" -react-navigation@^1.5.11: - version "1.5.11" - resolved "https://registry.yarnpkg.com/react-navigation/-/react-navigation-1.5.11.tgz#fba6ab45d7b9987c1763a5aaac53b4f6d62b7f5c" +react-navigation-deprecated-tab-navigator@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/react-navigation-deprecated-tab-navigator/-/react-navigation-deprecated-tab-navigator-1.3.0.tgz#015dcae1e977b984ca7e99245261c15439026bb7" + dependencies: + react-native-tab-view "^0.0.77" + +react-navigation-tabs@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/react-navigation-tabs/-/react-navigation-tabs-0.3.0.tgz#b1fe7ef1c665dd8928fafcc8622616e220ae5efa" + dependencies: + hoist-non-react-statics "^2.5.0" + prop-types "^15.6.0" + react-lifecycles-compat "^3.0.4" + react-native-safe-area-view "^0.7.0" + react-native-tab-view "~0.0.78" + +react-navigation@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-navigation/-/react-navigation-2.0.4.tgz#efc09de09799c2117a54002a6ea0e9b80e92828d" dependencies: clamp "^1.0.1" + create-react-context "^0.2.1" hoist-non-react-statics "^2.2.0" path-to-regexp "^1.7.0" prop-types "^15.5.10" - react-lifecycles-compat "^1.0.2" + react-lifecycles-compat "^3" react-native-drawer-layout-polyfill "^1.3.2" - react-native-safe-area-view "^0.7.0" - react-native-tab-view "github:react-navigation/react-native-tab-view" + react-native-safe-area-view "^0.8.0" + react-navigation-deprecated-tab-navigator "1.3.0" + react-navigation-tabs "0.3.0" react-proxy@^1.1.7: version "1.1.8" diff --git a/src/derived.js b/src/derived.js index bc3a4ed86cf7..4f63d40db58d 100644 --- a/src/derived.js +++ b/src/derived.js @@ -81,6 +81,41 @@ const interpolateInternal = function( ); }; -export const interpolate = function(value, inputRange, outputRange) { - return interpolateInternal(value, inputRange, outputRange); +export const Extrapolate = { + EXTEND: 'EXTEND', + CLAMP: 'CLAMP', + IDENTITY: 'IDENTITY', +}; + +export const interpolate = function(value, config) { + const { + inputRange, + outputRange, + extrapolate = Extrapolate.EXTEND, + extrapolateLeft, + extrapolateRight, + } = config; + const left = extrapolateLeft || extrapolate; + const right = extrapolateRight || extrapolate; + let output = interpolateInternal(value, inputRange, outputRange); + + if (left === Extrapolate.EXTEND) { + } else if (left === Extrapolate.CLAMP) { + output = max(outputRange[0], output); + } else if (left === Extrapolate.IDENTITY) { + output = cond(lessThan(output, outputRange[0]), value, output); + } + + if (right === Extrapolate.EXTEND) { + } else if (right === Extrapolate.CLAMP) { + output = min(outputRange[outputRange.length - 1], output); + } else if (right === Extrapolate.IDENTITY) { + output = cond( + greaterThan(output, outputRange[outputRange.length - 1]), + value, + output + ); + } + + return output; }; From e9807e22a6f497683300515e9cc4a2f38a8ecb63 Mon Sep 17 00:00:00 2001 From: Dylan Vann Date: Sun, 27 May 2018 22:49:29 -0400 Subject: [PATCH 4/8] Update readme. --- README.md | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 824737adf202..2212659d3086 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,6 @@ All the functionality that missing elements provide in Animated can be already a - [ ] value tracking (can be achieved in different way, reanimated also allows for tracking all the animation parameters not only destination params) - [ ] animation delays - [ ] animation staggering - - [ ] interpolate method ## Clocks @@ -422,6 +421,29 @@ Returns an accumulated value of the given node. This node stores a sum of all ev Works the same way as with the original Animated library. +--- +### `interpolate` +```js +interpolate(node, { + // Input range for the interpolation. Should be monotonically increasing. + inputRange: [nodeOrValue...], + // Output range for the interpolation, should be the same length as the input range. Should be monotonically increasing + outputRange: [nodeOrValue...], + // Sets the left and right extrapolate modes. + extrapolate?: Extrapolate.EXTEND | Extrapolate.CLAMP | Extrapolate.IDENTITY, + // Set the left extrapolate mode, the behavior if the output is less than the first value in outputRange. + extrapolateLeft?: Extrapolate.EXTEND | Extrapolate.CLAMP | Extrapolate.IDENTITY, + // Set the right extrapolate mode, the behavior if the output is greater than the last value in outputRange. + extrapolateRight?: Extrapolate.EXTEND | Extrapolate.CLAMP | Extrapolate.IDENTITY, +}) + +Extrapolate.EXTEND; // Will extend the range linearly. +Extrapolate.CLAMP; // Will clamp the range. +Extrapolate.IDENTITY; // Will return the input value if the output is out of range. +``` + +Maps an input value within a range to an output value within a range. Also supports different types of extrapolation for when the value falls outside the range. + --- @@ -434,7 +456,7 @@ decay(clock, { finished, velocity, position, time }, { deceleration }) Updates `position` and `velocity` nodes by running a single step of animation each time this node evaluates. State variable `finished` is set to `1` when the animation gets to the final point (that is the velocity drops under the level of significance). The `time` state node is populated automatically by this node and refers to the last clock time this node got evaluated. It is expected to be reset each time we want to restart the animation. Decay animation can be configured using `deceleration` config param and it controls how fast the animation decelerates. The value should be between `0` and `1` but only values that are close to `1` would yield meaningful results. --- -#### `timing` +### `timing` ```js timing(clock, { finished, position, frameTime, time }, { toValue, duration, easing }) @@ -444,7 +466,7 @@ Updates `position` node by running timing based animation from a given position The `frameTime` node will also get updated and represents the progress of animation in milliseconds (how long the animation has lasted so far). Similarly to the `time` node that just indicates the last clock time the animation node has been evaluated. Both of these variables are expected to be reset before restarting the animation. Finally `finished` node will be set to `1` when the position reaches the final value or when `frameTime` exceeds `duration`. --- -#### `spring` +### `spring` ```js spring(clock, { finished, position, velocity, time }, { damping, mass, stiffness, overshootClamping, restSpeedThreshold, restDisplacementThreshold, toValue }) From f2c20845a132270f803c322cdbf49860c810f070 Mon Sep 17 00:00:00 2001 From: Dylan Vann Date: Tue, 29 May 2018 09:36:10 -0400 Subject: [PATCH 5/8] Fix extrapolations. --- src/derived.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/derived.js b/src/derived.js index 4f63d40db58d..1bc423f704e0 100644 --- a/src/derived.js +++ b/src/derived.js @@ -101,17 +101,21 @@ export const interpolate = function(value, config) { if (left === Extrapolate.EXTEND) { } else if (left === Extrapolate.CLAMP) { - output = max(outputRange[0], output); + output = cond(lessThan(value, inputRange[0]), outputRange[0], output); } else if (left === Extrapolate.IDENTITY) { - output = cond(lessThan(output, outputRange[0]), value, output); + output = cond(lessThan(value, inputRange[0]), value, output); } if (right === Extrapolate.EXTEND) { } else if (right === Extrapolate.CLAMP) { - output = min(outputRange[outputRange.length - 1], output); + output = cond( + greaterThan(value, inputRange[inputRange.length - 1]), + outputRange[outputRange.length - 1], + output + ); } else if (right === Extrapolate.IDENTITY) { output = cond( - greaterThan(output, outputRange[outputRange.length - 1]), + greaterThan(value, inputRange[inputRange.length - 1]), value, output ); From 722b24a228e2fadb6e864b9915d28805cd2c5f9e Mon Sep 17 00:00:00 2001 From: Dylan Vann Date: Tue, 29 May 2018 09:38:29 -0400 Subject: [PATCH 6/8] Fix readme. --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2212659d3086..98e0f403903d 100644 --- a/README.md +++ b/README.md @@ -427,19 +427,19 @@ Works the same way as with the original Animated library. interpolate(node, { // Input range for the interpolation. Should be monotonically increasing. inputRange: [nodeOrValue...], - // Output range for the interpolation, should be the same length as the input range. Should be monotonically increasing + // Output range for the interpolation, should be the same length as the input range. outputRange: [nodeOrValue...], // Sets the left and right extrapolate modes. extrapolate?: Extrapolate.EXTEND | Extrapolate.CLAMP | Extrapolate.IDENTITY, - // Set the left extrapolate mode, the behavior if the output is less than the first value in outputRange. + // Set the left extrapolate mode, the behavior if the input is less than the first value in inputRange. extrapolateLeft?: Extrapolate.EXTEND | Extrapolate.CLAMP | Extrapolate.IDENTITY, - // Set the right extrapolate mode, the behavior if the output is greater than the last value in outputRange. + // Set the right extrapolate mode, the behavior if the input is greater than the last value in inputRange. extrapolateRight?: Extrapolate.EXTEND | Extrapolate.CLAMP | Extrapolate.IDENTITY, }) Extrapolate.EXTEND; // Will extend the range linearly. -Extrapolate.CLAMP; // Will clamp the range. -Extrapolate.IDENTITY; // Will return the input value if the output is out of range. +Extrapolate.CLAMP; // Will clamp the input value to the range. +Extrapolate.IDENTITY; // Will return the input value if the input value is out of range. ``` Maps an input value within a range to an output value within a range. Also supports different types of extrapolation for when the value falls outside the range. From febeb6a96c6c6bad22a22169a35e03aba17347e3 Mon Sep 17 00:00:00 2001 From: Dylan Vann Date: Tue, 29 May 2018 09:41:45 -0400 Subject: [PATCH 7/8] Remove metro-with-symlinks. --- Example/.gitignore | 3 --- Example/package.json | 5 ++--- Example/rn-cli.config.js | 3 --- Example/yarn.lock | 10 ---------- 4 files changed, 2 insertions(+), 19 deletions(-) delete mode 100644 Example/rn-cli.config.js diff --git a/Example/.gitignore b/Example/.gitignore index 555ded8afadf..5d647565fa9e 100644 --- a/Example/.gitignore +++ b/Example/.gitignore @@ -54,6 +54,3 @@ buck-out/ # Bundle artifact *.jsbundle - -# metro-with-symlinks -metro.config.js diff --git a/Example/package.json b/Example/package.json index a85ee08cd90b..858c0c70c992 100644 --- a/Example/package.json +++ b/Example/package.json @@ -3,9 +3,9 @@ "version": "0.0.1", "private": true, "scripts": { - "start": "metro-with-symlinks start", + "start": "node node_modules/react-native/local-cli/cli.js start", "test": "jest", - "postinstall": "metro-with-symlinks" + "postinstall": "rm -rf node_modules/react-native-reanimated/{.git,node_modules,Example}" }, "dependencies": { "react": "16.3.2", @@ -18,7 +18,6 @@ "babel-jest": "22.4.3", "babel-preset-react-native": "4.0.0", "jest": "22.4.3", - "metro-with-symlinks": "^1.0.12", "react-test-renderer": "16.3.1" }, "jest": { diff --git a/Example/rn-cli.config.js b/Example/rn-cli.config.js deleted file mode 100644 index 0f540cbc902d..000000000000 --- a/Example/rn-cli.config.js +++ /dev/null @@ -1,3 +0,0 @@ -const config = require('./metro.config.js') - -module.exports = config diff --git a/Example/yarn.lock b/Example/yarn.lock index 49cd3abcb5b5..fe971965a72c 100644 --- a/Example/yarn.lock +++ b/Example/yarn.lock @@ -1722,10 +1722,6 @@ decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" -dedent-js@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dedent-js/-/dedent-js-1.0.1.tgz#bee5fb7c9e727d85dffa24590d10ec1ab1255305" - deep-extend@~0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" @@ -3423,12 +3419,6 @@ metro-source-map@0.30.2: dependencies: source-map "^0.5.6" -metro-with-symlinks@^1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/metro-with-symlinks/-/metro-with-symlinks-1.0.12.tgz#d76599face0e6f6dfc849de042249427809ee48c" - dependencies: - dedent-js "^1.0.1" - metro@^0.30.0: version "0.30.2" resolved "https://registry.yarnpkg.com/metro/-/metro-0.30.2.tgz#e722e0eb106530f6d5bcf8de1f50353a0732cfb3" From 74a47421e4f39c58f1767997fde9860ca49ca46f Mon Sep 17 00:00:00 2001 From: Dylan Vann Date: Tue, 29 May 2018 09:42:57 -0400 Subject: [PATCH 8/8] Change name to getAnimation. --- Example/src/interpolate/AnimatedBounds.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Example/src/interpolate/AnimatedBounds.js b/Example/src/interpolate/AnimatedBounds.js index 3c429c827839..004757e1ecb6 100644 --- a/Example/src/interpolate/AnimatedBounds.js +++ b/Example/src/interpolate/AnimatedBounds.js @@ -95,7 +95,7 @@ function runTiming(clock, value, dest) { ]); } -const getThing = (min, max) => { +const getAnimation = (min, max) => { const clock = new Clock(); const state = { finished: new Value(1), @@ -176,8 +176,8 @@ export default class AnimatedBounds extends Component { extrapolate: 'clamp', }); - const min = getThing(-100, -50); - const max = getThing(100, 50); + const min = getAnimation(-100, -50); + const max = getAnimation(100, 50); this._transXA = interpolate(this._transX, { inputRange: [-100, 100], outputRange: [min, max],