[ReactNative] Move Animated to Open Source
Summary: Moves the files over and Exports Animated from 'react-native'.
This commit is contained in:
Родитель
d56ff42596
Коммит
b770310555
|
@ -0,0 +1,319 @@
|
||||||
|
/**
|
||||||
|
* The examples provided by Facebook are for non-commercial testing and
|
||||||
|
* evaluation purposes only.
|
||||||
|
*
|
||||||
|
* Facebook reserves all rights not expressly granted.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @providesModule AnExApp
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var React = require('react-native');
|
||||||
|
var {
|
||||||
|
Animated,
|
||||||
|
LayoutAnimation,
|
||||||
|
PanResponder,
|
||||||
|
StyleSheet,
|
||||||
|
View,
|
||||||
|
} = React;
|
||||||
|
|
||||||
|
var AnExSet = require('AnExSet');
|
||||||
|
|
||||||
|
var CIRCLE_SIZE = 80;
|
||||||
|
var CIRCLE_MARGIN = 18;
|
||||||
|
var NUM_CIRCLES = 30;
|
||||||
|
|
||||||
|
class Circle extends React.Component {
|
||||||
|
_onLongPress: () => void;
|
||||||
|
_toggleIsActive: () => void;
|
||||||
|
constructor(props: Object): void {
|
||||||
|
super();
|
||||||
|
this._onLongPress = this._onLongPress.bind(this);
|
||||||
|
this._toggleIsActive = this._toggleIsActive.bind(this);
|
||||||
|
this.state = {isActive: false};
|
||||||
|
this.state.pan = new Animated.ValueXY(); // Vectors reduce boilerplate. (step1: uncomment)
|
||||||
|
this.state.pop = new Animated.Value(0); // Initial value. (step2a: uncomment)
|
||||||
|
}
|
||||||
|
|
||||||
|
_onLongPress(): void {
|
||||||
|
var config = {tension: 40, friction: 3};
|
||||||
|
this.state.pan.addListener((value) => { // Async listener for state changes (step1: uncomment)
|
||||||
|
this.props.onMove && this.props.onMove(value);
|
||||||
|
});
|
||||||
|
Animated.spring(this.state.pop, {
|
||||||
|
toValue: 1, // Pop to larger size. (step2b: uncomment)
|
||||||
|
...config, // Reuse config for convenient consistency (step2b: uncomment)
|
||||||
|
}).start();
|
||||||
|
this.setState({panResponder: PanResponder.create({
|
||||||
|
onPanResponderMove: Animated.event([
|
||||||
|
null, // native event - ignore (step1: uncomment)
|
||||||
|
{dx: this.state.pan.x, dy: this.state.pan.y}, // links pan to gestureState (step1: uncomment)
|
||||||
|
]),
|
||||||
|
onPanResponderRelease: (e, gestureState) => {
|
||||||
|
LayoutAnimation.easeInEaseOut(); // @flowfixme animates layout update as one batch (step3: uncomment)
|
||||||
|
Animated.spring(this.state.pop, {
|
||||||
|
toValue: 0, // Pop back to 0 (step2c: uncomment)
|
||||||
|
...config,
|
||||||
|
}).start();
|
||||||
|
this.setState({panResponder: undefined});
|
||||||
|
this.props.onMove && this.props.onMove({
|
||||||
|
x: gestureState.dx + this.props.restLayout.x,
|
||||||
|
y: gestureState.dy + this.props.restLayout.y,
|
||||||
|
});
|
||||||
|
this.props.onDeactivate();
|
||||||
|
},
|
||||||
|
})}, () => {
|
||||||
|
this.props.onActivate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): ReactElement {
|
||||||
|
if (this.state.panResponder) {
|
||||||
|
var handlers = this.state.panResponder.panHandlers;
|
||||||
|
var dragStyle = { // Used to position while dragging
|
||||||
|
position: 'absolute', // Hoist out of layout (step1: uncomment)
|
||||||
|
...this.state.pan.getLayout(), // Convenience converter (step1: uncomment)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
handlers = {
|
||||||
|
onStartShouldSetResponder: () => !this.state.isActive,
|
||||||
|
onResponderGrant: () => {
|
||||||
|
this.state.pan.setValue({x: 0, y: 0}); // reset (step1: uncomment)
|
||||||
|
this.state.pan.setOffset(this.props.restLayout); // offset from onLayout (step1: uncomment)
|
||||||
|
this.longTimer = setTimeout(this._onLongPress, 300);
|
||||||
|
},
|
||||||
|
onResponderRelease: () => {
|
||||||
|
if (!this.state.panResponder) {
|
||||||
|
clearTimeout(this.longTimer);
|
||||||
|
this._toggleIsActive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var animatedStyle: Object = {
|
||||||
|
shadowOpacity: this.state.pop, // no need for interpolation (step2d: uncomment)
|
||||||
|
transform: [
|
||||||
|
{scale: this.state.pop.interpolate({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [1, 1.3] // scale up from 1 to 1.3 (step2d: uncomment)
|
||||||
|
})},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
var openVal = this.props.openVal;
|
||||||
|
if (this.props.dummy) {
|
||||||
|
animatedStyle.opacity = 0;
|
||||||
|
} else if (this.state.isActive) {
|
||||||
|
var innerOpenStyle = [styles.open, { // (step4: uncomment)
|
||||||
|
left: openVal.interpolate({inputRange: [0, 1], outputRange: [this.props.restLayout.x, 0]}),
|
||||||
|
top: openVal.interpolate({inputRange: [0, 1], outputRange: [this.props.restLayout.y, 0]}),
|
||||||
|
width: openVal.interpolate({inputRange: [0, 1], outputRange: [CIRCLE_SIZE, this.props.containerLayout.width]}),
|
||||||
|
height: openVal.interpolate({inputRange: [0, 1], outputRange: [CIRCLE_SIZE, this.props.containerLayout.height]}),
|
||||||
|
margin: openVal.interpolate({inputRange: [0, 1], outputRange: [CIRCLE_MARGIN, 0]}),
|
||||||
|
borderRadius: openVal.interpolate({inputRange: [-0.15, 0, 0.5, 1], outputRange: [0, CIRCLE_SIZE / 2, CIRCLE_SIZE * 1.3, 0]}),
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Animated.View
|
||||||
|
onLayout={this.props.onLayout}
|
||||||
|
style={[styles.dragView, dragStyle, animatedStyle, this.state.isActive ? styles.open : null]}
|
||||||
|
{...handlers}>
|
||||||
|
<Animated.View style={[styles.circle, innerOpenStyle]}>
|
||||||
|
<AnExSet
|
||||||
|
containerLayout={this.props.containerLayout}
|
||||||
|
id={this.props.id}
|
||||||
|
isActive={this.state.isActive}
|
||||||
|
openVal={this.props.openVal}
|
||||||
|
onDismiss={this._toggleIsActive}
|
||||||
|
/>
|
||||||
|
</Animated.View>
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_toggleIsActive(velocity) {
|
||||||
|
var config = {tension: 30, friction: 7};
|
||||||
|
if (this.state.isActive) {
|
||||||
|
Animated.spring(this.props.openVal, {toValue: 0, ...config}).start(() => { // (step4: uncomment)
|
||||||
|
this.setState({isActive: false}, this.props.onDeactivate);
|
||||||
|
}); // (step4: uncomment)
|
||||||
|
} else {
|
||||||
|
this.props.onActivate();
|
||||||
|
this.setState({isActive: true, panResponder: undefined}, () => {
|
||||||
|
// this.props.openVal.setValue(1); // (step4: comment)
|
||||||
|
Animated.spring(this.props.openVal, {toValue: 1, ...config}).start(); // (step4: uncomment)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnExApp extends React.Component {
|
||||||
|
_onMove: (position: Point) => void;
|
||||||
|
constructor(props: any): void {
|
||||||
|
super(props);
|
||||||
|
var keys = [];
|
||||||
|
for (var idx = 0; idx < NUM_CIRCLES; idx++) {
|
||||||
|
keys.push('E' + idx);
|
||||||
|
}
|
||||||
|
this.state = {
|
||||||
|
keys,
|
||||||
|
restLayouts: [],
|
||||||
|
openVal: new Animated.Value(0),
|
||||||
|
};
|
||||||
|
this._onMove = this._onMove.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): ReactElement {
|
||||||
|
var circles = this.state.keys.map((key, idx) => {
|
||||||
|
if (key === this.state.activeKey) {
|
||||||
|
return <Circle key={key + 'd'} dummy={true} />;
|
||||||
|
} else {
|
||||||
|
if (!this.state.restLayouts[idx]) {
|
||||||
|
var onLayout = function(index, e) {
|
||||||
|
var layout = e.nativeEvent.layout;
|
||||||
|
this.setState((state) => {
|
||||||
|
state.restLayouts[index] = layout;
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
}.bind(this, idx);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Circle
|
||||||
|
key={key}
|
||||||
|
id={key}
|
||||||
|
openVal={this.state.openVal}
|
||||||
|
onLayout={onLayout}
|
||||||
|
restLayout={this.state.restLayouts[idx]}
|
||||||
|
onActivate={this.setState.bind(this, {
|
||||||
|
activeKey: key,
|
||||||
|
activeInitialLayout: this.state.restLayouts[idx],
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (this.state.activeKey) {
|
||||||
|
circles.push(
|
||||||
|
<Animated.View key="dark" style={[styles.darkening, {opacity: this.state.openVal}]} />
|
||||||
|
);
|
||||||
|
circles.push(
|
||||||
|
<Circle
|
||||||
|
openVal={this.state.openVal}
|
||||||
|
key={this.state.activeKey}
|
||||||
|
id={this.state.activeKey}
|
||||||
|
restLayout={this.state.activeInitialLayout}
|
||||||
|
containerLayout={this.state.layout}
|
||||||
|
onMove={this._onMove}
|
||||||
|
onDeactivate={() => { this.setState({activeKey: undefined}); }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.grid} onLayout={(e) => this.setState({layout: e.nativeEvent.layout})}>
|
||||||
|
{circles}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onMove(position: Point): void {
|
||||||
|
var newKeys = moveToClosest(this.state, position);
|
||||||
|
if (newKeys !== this.state.keys) {
|
||||||
|
LayoutAnimation.easeInEaseOut(); // animates layout update as one batch (step3: uncomment)
|
||||||
|
this.setState({keys: newKeys});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Point = {x: number, y: number};
|
||||||
|
function distance(p1: Point, p2: Point): number {
|
||||||
|
var dx = p1.x - p2.x;
|
||||||
|
var dy = p1.y - p2.y;
|
||||||
|
return dx * dx + dy * dy;
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveToClosest({activeKey, keys, restLayouts}, position) {
|
||||||
|
var activeIdx = -1;
|
||||||
|
var closestIdx = activeIdx;
|
||||||
|
var minDist = Infinity;
|
||||||
|
var newKeys = [];
|
||||||
|
keys.forEach((key, idx) => {
|
||||||
|
var dist = distance(position, restLayouts[idx]);
|
||||||
|
if (key === activeKey) {
|
||||||
|
idx = activeIdx;
|
||||||
|
} else {
|
||||||
|
newKeys.push(key);
|
||||||
|
}
|
||||||
|
if (dist < minDist) {
|
||||||
|
minDist = dist;
|
||||||
|
closestIdx = idx;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (closestIdx === activeIdx) {
|
||||||
|
return keys; // nothing changed
|
||||||
|
} else {
|
||||||
|
newKeys.splice(closestIdx, 0, activeKey);
|
||||||
|
return newKeys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnExApp.title = 'Animated - Gratuitous App';
|
||||||
|
AnExApp.description = 'Bunch of Animations - tap a circle to ' +
|
||||||
|
'open a view with more animations, or longPress and drag to reorder circles.';
|
||||||
|
|
||||||
|
var styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
paddingTop: 64, // push content below nav bar
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
circle: {
|
||||||
|
width: CIRCLE_SIZE,
|
||||||
|
height: CIRCLE_SIZE,
|
||||||
|
borderRadius: CIRCLE_SIZE / 2,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'black',
|
||||||
|
margin: CIRCLE_MARGIN,
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
dragView: {
|
||||||
|
shadowRadius: 10,
|
||||||
|
shadowColor: 'rgba(0,0,0,0.7)',
|
||||||
|
shadowOffset: {height: 8},
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
open: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
width: undefined, // unset value from styles.circle
|
||||||
|
height: undefined, // unset value from styles.circle
|
||||||
|
borderRadius: 0, // unset value from styles.circle
|
||||||
|
},
|
||||||
|
darkening: {
|
||||||
|
backgroundColor: 'black',
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = AnExApp;
|
|
@ -0,0 +1,162 @@
|
||||||
|
/**
|
||||||
|
* The examples provided by Facebook are for non-commercial testing and
|
||||||
|
* evaluation purposes only.
|
||||||
|
*
|
||||||
|
* Facebook reserves all rights not expressly granted.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @providesModule AnExBobble
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var React = require('react-native');
|
||||||
|
var {
|
||||||
|
Animated,
|
||||||
|
Image,
|
||||||
|
PanResponder,
|
||||||
|
StyleSheet,
|
||||||
|
View,
|
||||||
|
} = React;
|
||||||
|
|
||||||
|
var NUM_BOBBLES = 5;
|
||||||
|
var RAD_EACH = Math.PI / 2 / (NUM_BOBBLES - 2);
|
||||||
|
var RADIUS = 160;
|
||||||
|
var BOBBLE_SPOTS = [...Array(NUM_BOBBLES)].map((_, i) => { // static positions
|
||||||
|
return i === 0 ? {x: 0, y: 0} : { // first bobble is the selector
|
||||||
|
x: -Math.cos(RAD_EACH * (i - 1)) * RADIUS,
|
||||||
|
y: -Math.sin(RAD_EACH * (i - 1)) * RADIUS,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
class AnExBobble extends React.Component {
|
||||||
|
constructor(props: Object) {
|
||||||
|
super(props);
|
||||||
|
this.state = {};
|
||||||
|
this.state.bobbles = BOBBLE_SPOTS.map((_, i) => {
|
||||||
|
return new Animated.ValueXY();
|
||||||
|
});
|
||||||
|
this.state.selectedBobble = null;
|
||||||
|
var bobblePanListener = (e, gestureState) => { // async events => change selection
|
||||||
|
var newSelected = computeNewSelected(gestureState);
|
||||||
|
if (this.state.selectedBobble !== newSelected) {
|
||||||
|
if (this.state.selectedBobble !== null) {
|
||||||
|
var restSpot = BOBBLE_SPOTS[this.state.selectedBobble];
|
||||||
|
Animated.spring(this.state.bobbles[this.state.selectedBobble], {
|
||||||
|
toValue: restSpot, // return previously selected bobble to rest position
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
if (newSelected !== null && newSelected !== 0) {
|
||||||
|
Animated.spring(this.state.bobbles[newSelected], {
|
||||||
|
toValue: this.state.bobbles[0], // newly selected should track the selector
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
this.state.selectedBobble = newSelected;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var releaseBobble = () => {
|
||||||
|
this.state.bobbles.forEach((bobble, i) => {
|
||||||
|
Animated.spring(bobble, {
|
||||||
|
toValue: {x: 0, y: 0} // all bobbles return to zero
|
||||||
|
}).start();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
this.state.bobbleResponder = PanResponder.create({
|
||||||
|
onStartShouldSetPanResponder: () => true,
|
||||||
|
onPanResponderGrant: () => {
|
||||||
|
BOBBLE_SPOTS.forEach((spot, idx) => {
|
||||||
|
Animated.spring(this.state.bobbles[idx], {
|
||||||
|
toValue: spot, // spring each bobble to its spot
|
||||||
|
friction: 3, // less friction => bouncier
|
||||||
|
}).start();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onPanResponderMove: Animated.event(
|
||||||
|
[ null, {dx: this.state.bobbles[0].x, dy: this.state.bobbles[0].y} ],
|
||||||
|
{listener: bobblePanListener} // async state changes with arbitrary logic
|
||||||
|
),
|
||||||
|
onPanResponderRelease: releaseBobble,
|
||||||
|
onPanResponderTerminate: releaseBobble,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): ReactElement {
|
||||||
|
return (
|
||||||
|
<View style={styles.bobbleContainer}>
|
||||||
|
{this.state.bobbles.map((_, i) => {
|
||||||
|
var j = this.state.bobbles.length - i - 1; // reverse so lead on top
|
||||||
|
var handlers = j > 0 ? {} : this.state.bobbleResponder.panHandlers;
|
||||||
|
return (
|
||||||
|
<Animated.Image
|
||||||
|
{...handlers}
|
||||||
|
source={{uri: BOBBLE_IMGS[j]}}
|
||||||
|
style={[styles.circle, {
|
||||||
|
backgroundColor: randColor(), // re-renders are obvious
|
||||||
|
transform: this.state.bobbles[j].getTranslateTransform(), // simple conversion
|
||||||
|
}]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var styles = StyleSheet.create({
|
||||||
|
circle: {
|
||||||
|
position: 'absolute',
|
||||||
|
height: 60,
|
||||||
|
width: 60,
|
||||||
|
borderRadius: 30,
|
||||||
|
borderWidth: 0.5,
|
||||||
|
},
|
||||||
|
bobbleContainer: {
|
||||||
|
top: -68,
|
||||||
|
paddingRight: 66,
|
||||||
|
flexDirection: 'row',
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function computeNewSelected(
|
||||||
|
gestureState: Object,
|
||||||
|
): ?number {
|
||||||
|
var {dx, dy} = gestureState;
|
||||||
|
var minDist = Infinity;
|
||||||
|
var newSelected = null;
|
||||||
|
var pointRadius = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
if (Math.abs(RADIUS - pointRadius) < 80) {
|
||||||
|
BOBBLE_SPOTS.forEach((spot, idx) => {
|
||||||
|
var delta = {x: spot.x - dx, y: spot.y - dy};
|
||||||
|
var dist = delta.x * delta.x + delta.y * delta.y;
|
||||||
|
if (dist < minDist) {
|
||||||
|
minDist = dist;
|
||||||
|
newSelected = idx;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return newSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
function randColor(): string {
|
||||||
|
var colors = [0,1,2].map(() => Math.floor(Math.random() * 150 + 100));
|
||||||
|
return 'rgb(' + colors.join(',') + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
var BOBBLE_IMGS = [
|
||||||
|
'https://scontent-sea1-1.xx.fbcdn.net/hphotos-xpf1/t39.1997-6/10173489_272703316237267_1025826781_n.png',
|
||||||
|
'https://scontent-sea1-1.xx.fbcdn.net/hphotos-xaf1/l/t39.1997-6/p240x240/851578_631487400212668_2087073502_n.png',
|
||||||
|
'https://scontent-sea1-1.xx.fbcdn.net/hphotos-xaf1/t39.1997-6/p240x240/851583_654446917903722_178118452_n.png',
|
||||||
|
'https://scontent-sea1-1.xx.fbcdn.net/hphotos-xaf1/t39.1997-6/p240x240/851565_641023175913294_875343096_n.png',
|
||||||
|
'https://scontent-sea1-1.xx.fbcdn.net/hphotos-xaf1/t39.1997-6/851562_575284782557566_1188781517_n.png',
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = AnExBobble;
|
|
@ -0,0 +1,114 @@
|
||||||
|
/**
|
||||||
|
* The examples provided by Facebook are for non-commercial testing and
|
||||||
|
* evaluation purposes only.
|
||||||
|
*
|
||||||
|
* Facebook reserves all rights not expressly granted.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @providesModule AnExChained
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var React = require('react-native');
|
||||||
|
var {
|
||||||
|
Animated,
|
||||||
|
Image,
|
||||||
|
PanResponder,
|
||||||
|
StyleSheet,
|
||||||
|
View,
|
||||||
|
} = React;
|
||||||
|
|
||||||
|
class AnExChained extends React.Component {
|
||||||
|
constructor(props: Object) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
stickers: [new Animated.ValueXY()], // 1 leader
|
||||||
|
};
|
||||||
|
var stickerConfig = {tension: 2, friction: 3}; // soft spring
|
||||||
|
for (var i = 0; i < 4; i++) { // 4 followers
|
||||||
|
var sticker = new Animated.ValueXY();
|
||||||
|
Animated.spring(sticker, {
|
||||||
|
...stickerConfig,
|
||||||
|
toValue: this.state.stickers[i], // Animated toValue's are tracked
|
||||||
|
}).start();
|
||||||
|
this.state.stickers.push(sticker); // push on the followers
|
||||||
|
}
|
||||||
|
var releaseChain = (e, gestureState) => {
|
||||||
|
this.state.stickers[0].flattenOffset(); // merges offset into value and resets
|
||||||
|
Animated.sequence([ // spring to start after decay finishes
|
||||||
|
Animated.decay(this.state.stickers[0], { // coast to a stop
|
||||||
|
velocity: {x: gestureState.vx, y: gestureState.vy},
|
||||||
|
deceleration: 0.997,
|
||||||
|
}),
|
||||||
|
Animated.spring(this.state.stickers[0], {
|
||||||
|
toValue: {x: 0, y: 0} // return to start
|
||||||
|
}),
|
||||||
|
]).start();
|
||||||
|
};
|
||||||
|
this.state.chainResponder = PanResponder.create({
|
||||||
|
onStartShouldSetPanResponder: () => true,
|
||||||
|
onPanResponderGrant: () => {
|
||||||
|
this.state.stickers[0].stopAnimation((value) => {
|
||||||
|
this.state.stickers[0].setOffset(value); // start where sticker animated to
|
||||||
|
this.state.stickers[0].setValue({x: 0, y: 0}); // avoid flicker before next event
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onPanResponderMove: Animated.event(
|
||||||
|
[null, {dx: this.state.stickers[0].x, dy: this.state.stickers[0].y}] // map gesture to leader
|
||||||
|
),
|
||||||
|
onPanResponderRelease: releaseChain,
|
||||||
|
onPanResponderTerminate: releaseChain,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<View style={styles.chained}>
|
||||||
|
{this.state.stickers.map((_, i) => {
|
||||||
|
var j = this.state.stickers.length - i - 1; // reverse so leader is on top
|
||||||
|
var handlers = (j === 0) ? this.state.chainResponder.panHandlers : {};
|
||||||
|
return (
|
||||||
|
<Animated.Image
|
||||||
|
{...handlers}
|
||||||
|
source={{uri: CHAIN_IMGS[j]}}
|
||||||
|
style={[styles.sticker, {
|
||||||
|
transform: this.state.stickers[j].getTranslateTransform(), // simple conversion
|
||||||
|
}]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var styles = StyleSheet.create({
|
||||||
|
chained: {
|
||||||
|
alignSelf: 'flex-end',
|
||||||
|
top: -160,
|
||||||
|
right: 126
|
||||||
|
},
|
||||||
|
sticker: {
|
||||||
|
position: 'absolute',
|
||||||
|
height: 120,
|
||||||
|
width: 120,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
var CHAIN_IMGS = [
|
||||||
|
'https://scontent-sea1-1.xx.fbcdn.net/hphotos-xpf1/t39.1997-6/p160x160/10574705_1529175770666007_724328156_n.png',
|
||||||
|
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-xfa1/t39.1997-6/p160x160/851575_392309884199657_1917957497_n.png',
|
||||||
|
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-xfa1/t39.1997-6/p160x160/851567_555288911225630_1628791128_n.png',
|
||||||
|
'https://scontent-sea1-1.xx.fbcdn.net/hphotos-xfa1/t39.1997-6/p160x160/851583_531111513625557_903469595_n.png',
|
||||||
|
'https://scontent-sea1-1.xx.fbcdn.net/hphotos-xpa1/t39.1997-6/p160x160/851577_510515972354399_2147096990_n.png',
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = AnExChained;
|
|
@ -0,0 +1,115 @@
|
||||||
|
/**
|
||||||
|
* The examples provided by Facebook are for non-commercial testing and
|
||||||
|
* evaluation purposes only.
|
||||||
|
*
|
||||||
|
* Facebook reserves all rights not expressly granted.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @providesModule AnExScroll
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var React = require('react-native');
|
||||||
|
var {
|
||||||
|
Animated,
|
||||||
|
Image,
|
||||||
|
ScrollView,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
View,
|
||||||
|
} = React;
|
||||||
|
|
||||||
|
class AnExScroll extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
scrollX: new Animated.Value(0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
var width = this.props.panelWidth;
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<ScrollView
|
||||||
|
automaticallyAdjustContentInsets={false}
|
||||||
|
scrollEventThrottle={16 /* get all events */ }
|
||||||
|
onScroll={Animated.event(
|
||||||
|
[{nativeEvent: {contentOffset: {x: this.state.scrollX}}}] // nested event mapping
|
||||||
|
)}
|
||||||
|
contentContainerStyle={{flex: 1, padding: 10}}
|
||||||
|
pagingEnabled={true}
|
||||||
|
horizontal={true}>
|
||||||
|
<View style={[styles.page, {width}]}>
|
||||||
|
<Image
|
||||||
|
style={{width: 180, height: 180}}
|
||||||
|
source={HAWK_PIC}
|
||||||
|
/>
|
||||||
|
<Text style={styles.text}>
|
||||||
|
{'I\'ll find something to put here.'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.page, {width}]}>
|
||||||
|
<Text style={styles.text}>{'And here.'}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.page, {width}]}>
|
||||||
|
<Text>{'But not here.'}</Text>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
<Animated.Image
|
||||||
|
pointerEvents="none"
|
||||||
|
style={[styles.bunny, {transform: [
|
||||||
|
{translateX: this.state.scrollX.interpolate({
|
||||||
|
inputRange: [0, width, 2 * width],
|
||||||
|
outputRange: [0, 0, width / 3]}), // multi-part ranges
|
||||||
|
extrapolate: 'clamp'}, // default is 'extend'
|
||||||
|
{translateY: this.state.scrollX.interpolate({
|
||||||
|
inputRange: [0, width, 2 * width],
|
||||||
|
outputRange: [0, -200, -260]}),
|
||||||
|
extrapolate: 'clamp'},
|
||||||
|
{scale: this.state.scrollX.interpolate({
|
||||||
|
inputRange: [0, width, 2 * width],
|
||||||
|
outputRange: [0.5, 0.5, 2]}),
|
||||||
|
extrapolate: 'clamp'},
|
||||||
|
]}]}
|
||||||
|
source={BUNNY_PIC}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
padding: 4,
|
||||||
|
paddingBottom: 10,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: 18,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
bunny: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
position: 'absolute',
|
||||||
|
height: 160,
|
||||||
|
width: 160,
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
var HAWK_PIC = {uri: 'https://scontent-sea1-1.xx.fbcdn.net/hphotos-xfa1/t39.1997-6/10734304_1562225620659674_837511701_n.png'};
|
||||||
|
var BUNNY_PIC = {uri: 'https://scontent-sea1-1.xx.fbcdn.net/hphotos-xaf1/t39.1997-6/851564_531111380292237_1898871086_n.png'};
|
||||||
|
|
||||||
|
module.exports = AnExScroll;
|
|
@ -0,0 +1,150 @@
|
||||||
|
/**
|
||||||
|
* The examples provided by Facebook are for non-commercial testing and
|
||||||
|
* evaluation purposes only.
|
||||||
|
*
|
||||||
|
* Facebook reserves all rights not expressly granted.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @providesModule AnExSet
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var React = require('react-native');
|
||||||
|
var {
|
||||||
|
Animated,
|
||||||
|
PanResponder,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
View,
|
||||||
|
} = React;
|
||||||
|
|
||||||
|
var AnExBobble = require('./AnExBobble');
|
||||||
|
var AnExChained = require('./AnExChained');
|
||||||
|
var AnExScroll = require('./AnExScroll');
|
||||||
|
var AnExTilt = require('./AnExTilt');
|
||||||
|
|
||||||
|
class AnExSet extends React.Component {
|
||||||
|
constructor(props: Object) {
|
||||||
|
super(props);
|
||||||
|
function randColor() {
|
||||||
|
var colors = [0,1,2].map(() => Math.floor(Math.random() * 150 + 100));
|
||||||
|
return 'rgb(' + colors.join(',') + ')';
|
||||||
|
}
|
||||||
|
this.state = {
|
||||||
|
closeColor: randColor(),
|
||||||
|
openColor: randColor(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
render(): ReactElement {
|
||||||
|
var backgroundColor = this.props.openVal ?
|
||||||
|
this.props.openVal.interpolate({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [
|
||||||
|
this.state.closeColor, // interpolates color strings
|
||||||
|
this.state.openColor
|
||||||
|
],
|
||||||
|
}) :
|
||||||
|
this.state.closeColor;
|
||||||
|
var panelWidth = this.props.containerLayout && this.props.containerLayout.width || 320;
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Animated.View
|
||||||
|
style={[styles.header, { backgroundColor }]}
|
||||||
|
{...this.state.dismissResponder.panHandlers}>
|
||||||
|
<Text style={[styles.text, styles.headerText]}>
|
||||||
|
{this.props.id}
|
||||||
|
</Text>
|
||||||
|
</Animated.View>
|
||||||
|
{this.props.isActive &&
|
||||||
|
<View style={styles.stream}>
|
||||||
|
<View style={styles.card}>
|
||||||
|
<Text style={styles.text}>
|
||||||
|
July 2nd
|
||||||
|
</Text>
|
||||||
|
<AnExTilt isActive={this.props.isActive} />
|
||||||
|
<AnExBobble />
|
||||||
|
</View>
|
||||||
|
<AnExScroll panelWidth={panelWidth}/>
|
||||||
|
<AnExChained />
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.state.dismissY = new Animated.Value(0);
|
||||||
|
this.state.dismissResponder = PanResponder.create({
|
||||||
|
onStartShouldSetPanResponder: () => this.props.isActive,
|
||||||
|
onPanResponderGrant: () => {
|
||||||
|
Animated.spring(this.props.openVal, { // Animated value passed in.
|
||||||
|
toValue: this.state.dismissY.interpolate({ // Track dismiss gesture
|
||||||
|
inputRange: [0, 300], // and interpolate pixel distance
|
||||||
|
outputRange: [1, 0], // to a fraction.
|
||||||
|
})
|
||||||
|
}).start();
|
||||||
|
},
|
||||||
|
onPanResponderMove: Animated.event(
|
||||||
|
[null, {dy: this.state.dismissY}] // track pan gesture
|
||||||
|
),
|
||||||
|
onPanResponderRelease: (e, gestureState) => {
|
||||||
|
if (gestureState.dy > 100) {
|
||||||
|
this.props.onDismiss(gestureState.vy); // delegates dismiss action to parent
|
||||||
|
} else {
|
||||||
|
Animated.spring(this.props.openVal, {
|
||||||
|
toValue: 1, // animate back open if released early
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingTop: 18,
|
||||||
|
height: 90,
|
||||||
|
},
|
||||||
|
stream: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: 'rgb(230, 230, 230)',
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
margin: 8,
|
||||||
|
padding: 8,
|
||||||
|
borderRadius: 6,
|
||||||
|
backgroundColor: 'white',
|
||||||
|
shadowRadius: 2,
|
||||||
|
shadowColor: 'black',
|
||||||
|
shadowOpacity: 0.2,
|
||||||
|
shadowOffset: {height: 0.5},
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
padding: 4,
|
||||||
|
paddingBottom: 10,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: 18,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
headerText: {
|
||||||
|
fontSize: 25,
|
||||||
|
color: 'white',
|
||||||
|
shadowRadius: 3,
|
||||||
|
shadowColor: 'black',
|
||||||
|
shadowOpacity: 1,
|
||||||
|
shadowOffset: {height: 1},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = AnExSet;
|
|
@ -0,0 +1,107 @@
|
||||||
|
<br /><br />
|
||||||
|
# React Native: Animated
|
||||||
|
|
||||||
|
ReactEurope 2015, Paris - Spencer Ahrens - Facebook
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
## Fluid Interactions
|
||||||
|
|
||||||
|
- People expect smooth, delightful experiences
|
||||||
|
- Complex interactions are hard
|
||||||
|
- Common patterns can be optimized
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
|
||||||
|
## Declarative Interactions
|
||||||
|
|
||||||
|
- Wire up inputs (events) to outputs (props) + transforms (springs, easing, etc.)
|
||||||
|
- Arbitrary code can define/update this config
|
||||||
|
- Config can be serialized -> native/main thread
|
||||||
|
- No refs or lifecycle to worry about
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
|
||||||
|
## var { Animated } = require('react-native');
|
||||||
|
|
||||||
|
- New library soon to be released for React Native
|
||||||
|
- 100% JS implementation -> X-Platform
|
||||||
|
- Per-platform native optimizations planned
|
||||||
|
- This talk -> usage examples, not implementation
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
|
||||||
|
## Gratuitous Animation Demo App
|
||||||
|
|
||||||
|
- Layout uses `flexWrap: 'wrap'`
|
||||||
|
- longPress -> drag to reorder
|
||||||
|
- Tap to open example sets
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
## Gratuitous Animation Codez
|
||||||
|
|
||||||
|
- Step 1: 2D tracking pan gesture
|
||||||
|
- Step 2: Simple pop-out spring on select
|
||||||
|
- Step 3: Animate grid reordering with `LayoutAnimation`
|
||||||
|
- Step 4: Opening animation
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
## Animation Example Set
|
||||||
|
|
||||||
|
- `Animated.Value` `this.props.open` passed in from parent
|
||||||
|
- `interpolate` works with string "shapes," e.g. `'rgb(0, 0, 255)'`, `'45deg'`
|
||||||
|
- Examples easily composed as separate components
|
||||||
|
- Dismissing tracks interpolated gesture
|
||||||
|
- Custom release logic
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
|
||||||
|
## Tilting Photo
|
||||||
|
|
||||||
|
- Pan -> translateX * 2, rotate, opacity (via tracking)
|
||||||
|
- Gesture release triggers separate animations
|
||||||
|
- `addListener` for async, arbitrary logic on animation progress
|
||||||
|
- `interpolate` easily creates parallax and other effects
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
## Bobbles
|
||||||
|
|
||||||
|
- Static positions defined
|
||||||
|
- Listens to events to maybe change selection
|
||||||
|
- Springs previous selection back
|
||||||
|
- New selection tracks selector
|
||||||
|
- `getTranslateTransform` adds convenience
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
## Chained
|
||||||
|
|
||||||
|
- Classic "Chat Heads" animation
|
||||||
|
- Each sticker tracks the one before it with a soft spring
|
||||||
|
- `decay` maintains gesture velocity, followed by `spring` to home
|
||||||
|
- `stopAnimation` provides the last value for `setOffset`
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
## Scrolling
|
||||||
|
|
||||||
|
- `Animated.event` can track all sorts of stuff
|
||||||
|
- Multi-part ranges and extrapolation options
|
||||||
|
- Transforms decompose into ordered components
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
# React Native: Animated
|
||||||
|
|
||||||
|
- Landing soon in master (days)
|
||||||
|
- GitHub: @vjeux, @sahrens
|
||||||
|
- Questions?
|
||||||
|
|
||||||
|
<br />
|
|
@ -0,0 +1,139 @@
|
||||||
|
/**
|
||||||
|
* The examples provided by Facebook are for non-commercial testing and
|
||||||
|
* evaluation purposes only.
|
||||||
|
*
|
||||||
|
* Facebook reserves all rights not expressly granted.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @providesModule AnExTilt
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var React = require('react-native');
|
||||||
|
var {
|
||||||
|
Animated,
|
||||||
|
Image,
|
||||||
|
PanResponder,
|
||||||
|
StyleSheet,
|
||||||
|
View,
|
||||||
|
} = React;
|
||||||
|
|
||||||
|
class AnExTilt extends React.Component {
|
||||||
|
constructor(props: Object) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
panX: new Animated.Value(0),
|
||||||
|
opacity: new Animated.Value(1),
|
||||||
|
burns: new Animated.Value(1.15),
|
||||||
|
};
|
||||||
|
this.state.tiltPanResponder = PanResponder.create({
|
||||||
|
onStartShouldSetPanResponder: () => true,
|
||||||
|
onPanResponderGrant: () => {
|
||||||
|
Animated.timing(this.state.opacity, {
|
||||||
|
toValue: this.state.panX.interpolate({
|
||||||
|
inputRange: [-300, 0, 300], // pan is in pixels
|
||||||
|
outputRange: [0, 1, 0], // goes to zero at both edges
|
||||||
|
}),
|
||||||
|
duration: 0, // direct tracking
|
||||||
|
}).start();
|
||||||
|
},
|
||||||
|
onPanResponderMove: Animated.event(
|
||||||
|
[null, {dx: this.state.panX}] // panX is linked to the gesture
|
||||||
|
),
|
||||||
|
onPanResponderRelease: (e, gestureState) => {
|
||||||
|
var toValue = 0;
|
||||||
|
if (gestureState.dx > 100) {
|
||||||
|
toValue = 500;
|
||||||
|
} else if (gestureState.dx < -100) {
|
||||||
|
toValue = -500;
|
||||||
|
}
|
||||||
|
Animated.spring(this.state.panX, {
|
||||||
|
toValue, // animate back to center or off screen
|
||||||
|
velocity: gestureState.vx, // maintain gesture velocity
|
||||||
|
tension: 10,
|
||||||
|
friction: 3,
|
||||||
|
}).start();
|
||||||
|
this.state.panX.removeAllListeners();
|
||||||
|
var id = this.state.panX.addListener(({value}) => { // listen until offscreen
|
||||||
|
if (Math.abs(value) > 400) {
|
||||||
|
this.state.panX.removeListener(id); // offscreen, so stop listening
|
||||||
|
Animated.timing(this.state.opacity, {
|
||||||
|
toValue: 1, // Fade back in. This unlinks it from tracking this.state.panX
|
||||||
|
}).start();
|
||||||
|
this.state.panX.setValue(0); // Note: stops the spring animation
|
||||||
|
toValue !== 0 && this._startBurnsZoom();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_startBurnsZoom() {
|
||||||
|
this.state.burns.setValue(1); // reset to beginning
|
||||||
|
Animated.decay(this.state.burns, {
|
||||||
|
velocity: 1, // sublte zoom
|
||||||
|
deceleration: 0.9999, // slow decay
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this._startBurnsZoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): ReactElement {
|
||||||
|
return (
|
||||||
|
<Animated.View
|
||||||
|
{...this.state.tiltPanResponder.panHandlers}
|
||||||
|
style={[styles.tilt, {
|
||||||
|
opacity: this.state.opacity,
|
||||||
|
transform: [
|
||||||
|
{rotate: this.state.panX.interpolate({
|
||||||
|
inputRange: [-320, 320],
|
||||||
|
outputRange: ['-15deg', '15deg']})}, // interpolate string "shapes"
|
||||||
|
{translateX: this.state.panX},
|
||||||
|
],
|
||||||
|
}]}>
|
||||||
|
<Animated.Image
|
||||||
|
pointerEvents="none"
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
transform: [
|
||||||
|
{translateX: this.state.panX.interpolate({
|
||||||
|
inputRange: [-3, 3], // small range is extended by default
|
||||||
|
outputRange: [2, -2]}) // parallax
|
||||||
|
},
|
||||||
|
{scale: this.state.burns.interpolate({
|
||||||
|
inputRange: [1, 3000],
|
||||||
|
outputRange: [1, 1.25]}) // simple multiplier
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
source={NATURE_IMAGE}
|
||||||
|
/>
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var styles = StyleSheet.create({
|
||||||
|
tilt: {
|
||||||
|
overflow: 'hidden',
|
||||||
|
height: 200,
|
||||||
|
marginBottom: 4,
|
||||||
|
backgroundColor: 'rgb(130, 130, 255)',
|
||||||
|
borderColor: 'rgba(0, 0, 0, 0.2)',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
var NATURE_IMAGE = {uri: 'http://www.deshow.net/d/file/travel/2009-04/scenic-beauty-of-nature-photography-2-504-4.jpg'};
|
||||||
|
|
||||||
|
module.exports = AnExTilt;
|
|
@ -50,6 +50,7 @@ var COMMON_COMPONENTS = [
|
||||||
];
|
];
|
||||||
|
|
||||||
var COMMON_APIS = [
|
var COMMON_APIS = [
|
||||||
|
require('./AnimationExample/AnExApp'),
|
||||||
require('./GeolocationExample'),
|
require('./GeolocationExample'),
|
||||||
require('./LayoutExample'),
|
require('./LayoutExample'),
|
||||||
require('./PanResponderExample'),
|
require('./PanResponderExample'),
|
||||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,148 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
* @providesModule Easing
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var bezier = require('bezier');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class implements common easing functions. The math is pretty obscure,
|
||||||
|
* but this cool website has nice visual illustrations of what they represent:
|
||||||
|
* http://xaedes.de/dev/transitions/
|
||||||
|
*/
|
||||||
|
class Easing {
|
||||||
|
static step0(n) {
|
||||||
|
return n > 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static step1(n) {
|
||||||
|
return n >= 1 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static linear(t) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ease(t: number): number {
|
||||||
|
return ease(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
static quad(t) {
|
||||||
|
return t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static cubic(t) {
|
||||||
|
return t * t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static poly(n) {
|
||||||
|
return (t) => Math.pow(t, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static sin(t) {
|
||||||
|
return 1 - Math.cos(t * Math.PI / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static circle(t) {
|
||||||
|
return 1 - Math.sqrt(1 - t * t);
|
||||||
|
}
|
||||||
|
|
||||||
|
static exp(t) {
|
||||||
|
return Math.pow(2, 10 * (t - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
static elastic(a: number, p: number): (t: number) => number {
|
||||||
|
var tau = Math.PI * 2;
|
||||||
|
// flow isn't smart enough to figure out that s is always assigned to a
|
||||||
|
// number before being used in the returned function
|
||||||
|
var s: any;
|
||||||
|
if (arguments.length < 2) {
|
||||||
|
p = 0.45;
|
||||||
|
}
|
||||||
|
if (arguments.length) {
|
||||||
|
s = p / tau * Math.asin(1 / a);
|
||||||
|
} else {
|
||||||
|
a = 1;
|
||||||
|
s = p / 4;
|
||||||
|
}
|
||||||
|
return (t) => 1 + a * Math.pow(2, -10 * t) * Math.sin((t - s) * tau / p);
|
||||||
|
};
|
||||||
|
|
||||||
|
static back(s: number): (t: number) => number {
|
||||||
|
if (s === undefined) {
|
||||||
|
s = 1.70158;
|
||||||
|
}
|
||||||
|
return (t) => t * t * ((s + 1) * t - s);
|
||||||
|
};
|
||||||
|
|
||||||
|
static bounce(t: number): number {
|
||||||
|
if (t < 1 / 2.75) {
|
||||||
|
return 7.5625 * t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t < 2 / 2.75) {
|
||||||
|
t -= 1.5 / 2.75;
|
||||||
|
return 7.5625 * t * t + 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t < 2.5 / 2.75) {
|
||||||
|
t -= 2.25 / 2.75;
|
||||||
|
return 7.5625 * t * t + 0.9375;
|
||||||
|
}
|
||||||
|
|
||||||
|
t -= 2.625 / 2.75;
|
||||||
|
return 7.5625 * t * t + 0.984375;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bezier(
|
||||||
|
x1: number,
|
||||||
|
y1: number,
|
||||||
|
x2: number,
|
||||||
|
y2: number,
|
||||||
|
epsilon?: ?number,
|
||||||
|
): (t: number) => number {
|
||||||
|
if (epsilon === undefined) {
|
||||||
|
// epsilon determines the precision of the solved values
|
||||||
|
// a good approximation is:
|
||||||
|
var duration = 500; // duration of animation in milliseconds.
|
||||||
|
epsilon = (1000 / 60 / duration) / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bezier(x1, y1, x2, y2, epsilon);
|
||||||
|
}
|
||||||
|
|
||||||
|
static in(
|
||||||
|
easing: (t: number) => number,
|
||||||
|
): (t: number) => number {
|
||||||
|
return easing;
|
||||||
|
}
|
||||||
|
|
||||||
|
static out(
|
||||||
|
easing: (t: number) => number,
|
||||||
|
): (t: number) => number {
|
||||||
|
return (t) => 1 - easing(1 - t);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inOut(
|
||||||
|
easing: (t: number) => number,
|
||||||
|
): (t: number) => number {
|
||||||
|
return (t) => {
|
||||||
|
if (t < 0.5) {
|
||||||
|
return easing(t * 2) / 2;
|
||||||
|
}
|
||||||
|
return 1 - easing((1 - t) * 2) / 2;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ease = Easing.bezier(0.42, 0, 1, 1);
|
||||||
|
|
||||||
|
module.exports = Easing;
|
|
@ -0,0 +1,258 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
* @providesModule Interpolation
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var invariant = require('invariant');
|
||||||
|
|
||||||
|
type ExtrapolateType = 'extend' | 'identity' | 'clamp';
|
||||||
|
|
||||||
|
// $FlowFixMe D2163827
|
||||||
|
export type InterpolationConfigType = {
|
||||||
|
inputRange: Array<number>;
|
||||||
|
outputRange: (Array<number> | Array<string>);
|
||||||
|
easing?: ((input: number) => number);
|
||||||
|
extrapolate?: ExtrapolateType;
|
||||||
|
extrapolateLeft?: ExtrapolateType;
|
||||||
|
extrapolateRight?: ExtrapolateType;
|
||||||
|
};
|
||||||
|
|
||||||
|
var linear = (t) => t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Very handy helper to map input ranges to output ranges with an easing
|
||||||
|
* function and custom behavior outside of the ranges.
|
||||||
|
*/
|
||||||
|
class Interpolation {
|
||||||
|
static create(config: InterpolationConfigType): (input: number) => number | string {
|
||||||
|
|
||||||
|
if (config.outputRange && typeof config.outputRange[0] === 'string') {
|
||||||
|
return createInterpolationFromStringOutputRange(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputRange: Array<number> = (config.outputRange: any);
|
||||||
|
checkInfiniteRange('outputRange', outputRange);
|
||||||
|
|
||||||
|
var inputRange = config.inputRange;
|
||||||
|
checkInfiniteRange('inputRange', inputRange);
|
||||||
|
checkValidInputRange(inputRange);
|
||||||
|
|
||||||
|
invariant(
|
||||||
|
inputRange.length === outputRange.length,
|
||||||
|
'inputRange (' + inputRange.length + ') and outputRange (' +
|
||||||
|
outputRange.length + ') must have the same length'
|
||||||
|
);
|
||||||
|
|
||||||
|
var easing = config.easing || linear;
|
||||||
|
|
||||||
|
var extrapolateLeft: ExtrapolateType = 'extend';
|
||||||
|
if (config.extrapolateLeft !== undefined) {
|
||||||
|
extrapolateLeft = config.extrapolateLeft;
|
||||||
|
} else if (config.extrapolate !== undefined) {
|
||||||
|
extrapolateLeft = config.extrapolate;
|
||||||
|
}
|
||||||
|
|
||||||
|
var extrapolateRight: ExtrapolateType = 'extend';
|
||||||
|
if (config.extrapolateRight !== undefined) {
|
||||||
|
extrapolateRight = config.extrapolateRight;
|
||||||
|
} else if (config.extrapolate !== undefined) {
|
||||||
|
extrapolateRight = config.extrapolate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (input) => {
|
||||||
|
invariant(
|
||||||
|
typeof input === 'number',
|
||||||
|
'Cannot interpolation an input which is not a number'
|
||||||
|
);
|
||||||
|
|
||||||
|
var range = findRange(input, inputRange);
|
||||||
|
return interpolate(
|
||||||
|
input,
|
||||||
|
inputRange[range],
|
||||||
|
inputRange[range + 1],
|
||||||
|
outputRange[range],
|
||||||
|
outputRange[range + 1],
|
||||||
|
easing,
|
||||||
|
extrapolateLeft,
|
||||||
|
extrapolateRight,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function interpolate(
|
||||||
|
input: number,
|
||||||
|
inputMin: number,
|
||||||
|
inputMax: number,
|
||||||
|
outputMin: number,
|
||||||
|
outputMax: number,
|
||||||
|
easing: ((input: number) => number),
|
||||||
|
extrapolateLeft: ExtrapolateType,
|
||||||
|
extrapolateRight: ExtrapolateType,
|
||||||
|
) {
|
||||||
|
var result = input;
|
||||||
|
|
||||||
|
// Extrapolate
|
||||||
|
if (result < inputMin) {
|
||||||
|
if (extrapolateLeft === 'identity') {
|
||||||
|
return result;
|
||||||
|
} else if (extrapolateLeft === 'clamp') {
|
||||||
|
result = inputMin;
|
||||||
|
} else if (extrapolateLeft === 'extend') {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result > inputMax) {
|
||||||
|
if (extrapolateRight === 'identity') {
|
||||||
|
return result;
|
||||||
|
} else if (extrapolateRight === 'clamp') {
|
||||||
|
result = inputMax;
|
||||||
|
} else if (extrapolateRight === 'extend') {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputMin === outputMax) {
|
||||||
|
return outputMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputMin === inputMax) {
|
||||||
|
if (input <= inputMin) {
|
||||||
|
return outputMin;
|
||||||
|
}
|
||||||
|
return outputMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input Range
|
||||||
|
if (inputMin === -Infinity) {
|
||||||
|
result = -result;
|
||||||
|
} else if (inputMax === Infinity) {
|
||||||
|
result = result - inputMin;
|
||||||
|
} else {
|
||||||
|
result = (result - inputMin) / (inputMax - inputMin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Easing
|
||||||
|
result = easing(result);
|
||||||
|
|
||||||
|
// Output Range
|
||||||
|
if (outputMin === -Infinity) {
|
||||||
|
result = -result;
|
||||||
|
} else if (outputMax === Infinity) {
|
||||||
|
result = result + outputMin;
|
||||||
|
} else {
|
||||||
|
result = result * (outputMax - outputMin) + outputMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringShapeRegex = /[0-9\.-]+/g;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supports string shapes by extracting numbers so new values can be computed,
|
||||||
|
* and recombines those values into new strings of the same shape. Supports
|
||||||
|
* things like:
|
||||||
|
*
|
||||||
|
* rgba(123, 42, 99, 0.36) // colors
|
||||||
|
* -45deg // values with units
|
||||||
|
*/
|
||||||
|
function createInterpolationFromStringOutputRange(
|
||||||
|
config: InterpolationConfigType,
|
||||||
|
): (input: number) => string {
|
||||||
|
var outputRange: Array<string> = (config.outputRange: any);
|
||||||
|
invariant(outputRange.length >= 2, 'Bad output range');
|
||||||
|
checkPattern(outputRange);
|
||||||
|
|
||||||
|
// ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)']
|
||||||
|
// ->
|
||||||
|
// [
|
||||||
|
// [0, 50],
|
||||||
|
// [100, 150],
|
||||||
|
// [200, 250],
|
||||||
|
// [0, 0.5],
|
||||||
|
// ]
|
||||||
|
var outputRanges = outputRange[0].match(stringShapeRegex).map(() => []);
|
||||||
|
outputRange.forEach(value => {
|
||||||
|
value.match(stringShapeRegex).forEach((number, i) => {
|
||||||
|
outputRanges[i].push(+number);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var interpolations = outputRange[0].match(stringShapeRegex).map((value, i) => {
|
||||||
|
return Interpolation.create({
|
||||||
|
...config,
|
||||||
|
outputRange: outputRanges[i],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (input) => {
|
||||||
|
var i = 0;
|
||||||
|
// 'rgba(0, 100, 200, 0)'
|
||||||
|
// ->
|
||||||
|
// 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...'
|
||||||
|
return outputRange[0].replace(stringShapeRegex, () => {
|
||||||
|
return String(interpolations[i++](input));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkPattern(arr: Array<string>) {
|
||||||
|
var pattern = arr[0].replace(stringShapeRegex, '');
|
||||||
|
for (var i = 1; i < arr.length; ++i) {
|
||||||
|
invariant(
|
||||||
|
pattern === arr[i].replace(stringShapeRegex, ''),
|
||||||
|
'invalid pattern ' + arr[0] + ' and ' + arr[i],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findRange(input: number, inputRange: Array<number>) {
|
||||||
|
for (var i = 1; i < inputRange.length - 1; ++i) {
|
||||||
|
if (inputRange[i] >= input) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkValidInputRange(arr: Array<number>) {
|
||||||
|
invariant(arr.length >= 2, 'inputRange must have at least 2 elements');
|
||||||
|
for (var i = 1; i < arr.length; ++i) {
|
||||||
|
invariant(
|
||||||
|
arr[i] >= arr[i - 1],
|
||||||
|
/* $FlowFixMe(>=0.13.0) - In the addition expression below this comment,
|
||||||
|
* one or both of the operands may be something that doesn't cleanly
|
||||||
|
* convert to a string, like undefined, null, and object, etc. If you really
|
||||||
|
* mean this implicit string conversion, you can do something like
|
||||||
|
* String(myThing)
|
||||||
|
*/
|
||||||
|
'inputRange must be monolithically increasing ' + arr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkInfiniteRange(name: string, arr: Array<number>) {
|
||||||
|
invariant(arr.length >= 2, name + ' must have at least 2 elements');
|
||||||
|
invariant(
|
||||||
|
arr.length !== 2 || arr[0] !== -Infinity || arr[1] !== Infinity,
|
||||||
|
/* $FlowFixMe(>=0.13.0) - In the addition expression below this comment,
|
||||||
|
* one or both of the operands may be something that doesn't cleanly convert
|
||||||
|
* to a string, like undefined, null, and object, etc. If you really mean
|
||||||
|
* this implicit string conversion, you can do something like
|
||||||
|
* String(myThing)
|
||||||
|
*/
|
||||||
|
name + 'cannot be ]-infinity;+infinity[ ' + arr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Interpolation;
|
|
@ -0,0 +1,449 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
jest
|
||||||
|
.autoMockOff()
|
||||||
|
.setMock('Text', {})
|
||||||
|
.setMock('View', {})
|
||||||
|
.setMock('Image', {})
|
||||||
|
.setMock('React', {Component: class {}});
|
||||||
|
|
||||||
|
var Animated = require('Animated');
|
||||||
|
|
||||||
|
describe('Animated', () => {
|
||||||
|
it('works end to end', () => {
|
||||||
|
var anim = new Animated.Value(0);
|
||||||
|
|
||||||
|
var callback = jest.genMockFunction();
|
||||||
|
|
||||||
|
var node = new Animated.__PropsOnlyForTests({
|
||||||
|
style: {
|
||||||
|
backgroundColor: 'red',
|
||||||
|
opacity: anim,
|
||||||
|
transform: [
|
||||||
|
{translateX: anim.interpolate({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [100, 200],
|
||||||
|
})},
|
||||||
|
{scale: anim},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}, callback);
|
||||||
|
|
||||||
|
expect(anim.getChildren().length).toBe(3);
|
||||||
|
|
||||||
|
expect(node.__getValue()).toEqual({
|
||||||
|
style: {
|
||||||
|
backgroundColor: 'red',
|
||||||
|
opacity: 0,
|
||||||
|
transform: [
|
||||||
|
{translateX: 100},
|
||||||
|
{scale: 0},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
anim.setValue(0.5);
|
||||||
|
|
||||||
|
expect(callback).toBeCalled();
|
||||||
|
|
||||||
|
expect(node.__getValue()).toEqual({
|
||||||
|
style: {
|
||||||
|
backgroundColor: 'red',
|
||||||
|
opacity: 0.5,
|
||||||
|
transform: [
|
||||||
|
{translateX: 150},
|
||||||
|
{scale: 0.5},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
node.detach();
|
||||||
|
expect(anim.getChildren().length).toBe(0);
|
||||||
|
|
||||||
|
anim.setValue(1);
|
||||||
|
expect(callback.mock.calls.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not detach on updates', () => {
|
||||||
|
var anim = new Animated.Value(0);
|
||||||
|
anim.detach = jest.genMockFunction();
|
||||||
|
|
||||||
|
var c = new Animated.View();
|
||||||
|
c.props = {
|
||||||
|
style: {
|
||||||
|
opacity: anim,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
c.componentWillMount();
|
||||||
|
|
||||||
|
expect(anim.detach).not.toBeCalled();
|
||||||
|
c.componentWillReceiveProps({
|
||||||
|
style: {
|
||||||
|
opacity: anim,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(anim.detach).not.toBeCalled();
|
||||||
|
|
||||||
|
c.componentWillUnmount();
|
||||||
|
expect(anim.detach).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('stops animation when detached', () => {
|
||||||
|
// jest environment doesn't have requestAnimationFrame :(
|
||||||
|
window.requestAnimationFrame = jest.genMockFunction();
|
||||||
|
window.cancelAnimationFrame = jest.genMockFunction();
|
||||||
|
|
||||||
|
var anim = new Animated.Value(0);
|
||||||
|
var callback = jest.genMockFunction();
|
||||||
|
|
||||||
|
var c = new Animated.View();
|
||||||
|
c.props = {
|
||||||
|
style: {
|
||||||
|
opacity: anim,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
c.componentWillMount();
|
||||||
|
|
||||||
|
Animated.timing(anim, {toValue: 10, duration: 1000}).start(callback);
|
||||||
|
|
||||||
|
c.componentWillUnmount();
|
||||||
|
|
||||||
|
expect(callback).toBeCalledWith({finished: false});
|
||||||
|
expect(callback).toBeCalledWith({finished: false});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('triggers callback when spring is at rest', () => {
|
||||||
|
var anim = new Animated.Value(0);
|
||||||
|
var callback = jest.genMockFunction();
|
||||||
|
Animated.spring(anim, {toValue: 0, velocity: 0}).start(callback);
|
||||||
|
expect(callback).toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('Animated Sequence', () => {
|
||||||
|
|
||||||
|
it('works with an empty sequence', () => {
|
||||||
|
var cb = jest.genMockFunction();
|
||||||
|
Animated.sequence([]).start(cb);
|
||||||
|
expect(cb).toBeCalledWith({finished: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sequences well', () => {
|
||||||
|
var anim1 = {start: jest.genMockFunction()};
|
||||||
|
var anim2 = {start: jest.genMockFunction()};
|
||||||
|
var cb = jest.genMockFunction();
|
||||||
|
|
||||||
|
var seq = Animated.sequence([anim1, anim2]);
|
||||||
|
|
||||||
|
expect(anim1.start).not.toBeCalled();
|
||||||
|
expect(anim2.start).not.toBeCalled();
|
||||||
|
|
||||||
|
seq.start(cb);
|
||||||
|
|
||||||
|
expect(anim1.start).toBeCalled();
|
||||||
|
expect(anim2.start).not.toBeCalled();
|
||||||
|
expect(cb).not.toBeCalled();
|
||||||
|
|
||||||
|
anim1.start.mock.calls[0][0]({finished: true});
|
||||||
|
|
||||||
|
expect(anim2.start).toBeCalled();
|
||||||
|
expect(cb).not.toBeCalled();
|
||||||
|
|
||||||
|
anim2.start.mock.calls[0][0]({finished: true});
|
||||||
|
expect(cb).toBeCalledWith({finished: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports interrupting sequence', () => {
|
||||||
|
var anim1 = {start: jest.genMockFunction()};
|
||||||
|
var anim2 = {start: jest.genMockFunction()};
|
||||||
|
var cb = jest.genMockFunction();
|
||||||
|
|
||||||
|
Animated.sequence([anim1, anim2]).start(cb);
|
||||||
|
|
||||||
|
anim1.start.mock.calls[0][0]({finished: false});
|
||||||
|
|
||||||
|
expect(anim1.start).toBeCalled();
|
||||||
|
expect(anim2.start).not.toBeCalled();
|
||||||
|
expect(cb).toBeCalledWith({finished: false});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports stopping sequence', () => {
|
||||||
|
var anim1 = {start: jest.genMockFunction(), stop: jest.genMockFunction()};
|
||||||
|
var anim2 = {start: jest.genMockFunction(), stop: jest.genMockFunction()};
|
||||||
|
var cb = jest.genMockFunction();
|
||||||
|
|
||||||
|
var seq = Animated.sequence([anim1, anim2]);
|
||||||
|
seq.start(cb);
|
||||||
|
seq.stop();
|
||||||
|
|
||||||
|
expect(anim1.stop).toBeCalled();
|
||||||
|
expect(anim2.stop).not.toBeCalled();
|
||||||
|
expect(cb).not.toBeCalled();
|
||||||
|
|
||||||
|
anim1.start.mock.calls[0][0]({finished: false});
|
||||||
|
|
||||||
|
expect(cb).toBeCalledWith({finished: false});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('Animated Parallel', () => {
|
||||||
|
|
||||||
|
it('works with an empty parallel', () => {
|
||||||
|
var cb = jest.genMockFunction();
|
||||||
|
Animated.parallel([]).start(cb);
|
||||||
|
expect(cb).toBeCalledWith({finished: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('parellelizes well', () => {
|
||||||
|
var anim1 = {start: jest.genMockFunction()};
|
||||||
|
var anim2 = {start: jest.genMockFunction()};
|
||||||
|
var cb = jest.genMockFunction();
|
||||||
|
|
||||||
|
var par = Animated.parallel([anim1, anim2]);
|
||||||
|
|
||||||
|
expect(anim1.start).not.toBeCalled();
|
||||||
|
expect(anim2.start).not.toBeCalled();
|
||||||
|
|
||||||
|
par.start(cb);
|
||||||
|
|
||||||
|
expect(anim1.start).toBeCalled();
|
||||||
|
expect(anim2.start).toBeCalled();
|
||||||
|
expect(cb).not.toBeCalled();
|
||||||
|
|
||||||
|
anim1.start.mock.calls[0][0]({finished: true});
|
||||||
|
expect(cb).not.toBeCalled();
|
||||||
|
|
||||||
|
anim2.start.mock.calls[0][0]({finished: true});
|
||||||
|
expect(cb).toBeCalledWith({finished: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports stopping parallel', () => {
|
||||||
|
var anim1 = {start: jest.genMockFunction(), stop: jest.genMockFunction()};
|
||||||
|
var anim2 = {start: jest.genMockFunction(), stop: jest.genMockFunction()};
|
||||||
|
var cb = jest.genMockFunction();
|
||||||
|
|
||||||
|
var seq = Animated.parallel([anim1, anim2]);
|
||||||
|
seq.start(cb);
|
||||||
|
seq.stop();
|
||||||
|
|
||||||
|
expect(anim1.stop).toBeCalled();
|
||||||
|
expect(anim2.stop).toBeCalled();
|
||||||
|
expect(cb).not.toBeCalled();
|
||||||
|
|
||||||
|
anim1.start.mock.calls[0][0]({finished: false});
|
||||||
|
expect(cb).not.toBeCalled();
|
||||||
|
|
||||||
|
anim2.start.mock.calls[0][0]({finished: false});
|
||||||
|
expect(cb).toBeCalledWith({finished: false});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('does not call stop more than once when stopping', () => {
|
||||||
|
var anim1 = {start: jest.genMockFunction(), stop: jest.genMockFunction()};
|
||||||
|
var anim2 = {start: jest.genMockFunction(), stop: jest.genMockFunction()};
|
||||||
|
var anim3 = {start: jest.genMockFunction(), stop: jest.genMockFunction()};
|
||||||
|
var cb = jest.genMockFunction();
|
||||||
|
|
||||||
|
var seq = Animated.parallel([anim1, anim2, anim3]);
|
||||||
|
seq.start(cb);
|
||||||
|
|
||||||
|
anim1.start.mock.calls[0][0]({finished: false});
|
||||||
|
|
||||||
|
expect(anim1.stop.mock.calls.length).toBe(0);
|
||||||
|
expect(anim2.stop.mock.calls.length).toBe(1);
|
||||||
|
expect(anim3.stop.mock.calls.length).toBe(1);
|
||||||
|
|
||||||
|
anim2.start.mock.calls[0][0]({finished: false});
|
||||||
|
|
||||||
|
expect(anim1.stop.mock.calls.length).toBe(0);
|
||||||
|
expect(anim2.stop.mock.calls.length).toBe(1);
|
||||||
|
expect(anim3.stop.mock.calls.length).toBe(1);
|
||||||
|
|
||||||
|
anim3.start.mock.calls[0][0]({finished: false});
|
||||||
|
|
||||||
|
expect(anim1.stop.mock.calls.length).toBe(0);
|
||||||
|
expect(anim2.stop.mock.calls.length).toBe(1);
|
||||||
|
expect(anim3.stop.mock.calls.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Animated Events', () => {
|
||||||
|
it('should map events', () => {
|
||||||
|
var value = new Animated.Value(0);
|
||||||
|
var handler = Animated.event(
|
||||||
|
[null, {state: {foo: value}}],
|
||||||
|
);
|
||||||
|
handler({bar: 'ignoreBar'}, {state: {baz: 'ignoreBaz', foo: 42}});
|
||||||
|
expect(value.__getValue()).toBe(42);
|
||||||
|
});
|
||||||
|
it('should call listeners', () => {
|
||||||
|
var value = new Animated.Value(0);
|
||||||
|
var listener = jest.genMockFunction();
|
||||||
|
var handler = Animated.event(
|
||||||
|
[{foo: value}],
|
||||||
|
{listener},
|
||||||
|
);
|
||||||
|
handler({foo: 42});
|
||||||
|
expect(value.__getValue()).toBe(42);
|
||||||
|
expect(listener.mock.calls.length).toBe(1);
|
||||||
|
expect(listener).toBeCalledWith({foo: 42});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Animated Tracking', () => {
|
||||||
|
it('should track values', () => {
|
||||||
|
var value1 = new Animated.Value(0);
|
||||||
|
var value2 = new Animated.Value(0);
|
||||||
|
Animated.timing(value2, {
|
||||||
|
toValue: value1,
|
||||||
|
duration: 0,
|
||||||
|
}).start();
|
||||||
|
value1.setValue(42);
|
||||||
|
expect(value2.__getValue()).toBe(42);
|
||||||
|
value1.setValue(7);
|
||||||
|
expect(value2.__getValue()).toBe(7);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should track interpolated values', () => {
|
||||||
|
var value1 = new Animated.Value(0);
|
||||||
|
var value2 = new Animated.Value(0);
|
||||||
|
Animated.timing(value2, {
|
||||||
|
toValue: value1.interpolate({
|
||||||
|
inputRange: [0, 2],
|
||||||
|
outputRange: [0, 1]
|
||||||
|
}),
|
||||||
|
duration: 0,
|
||||||
|
}).start();
|
||||||
|
value1.setValue(42);
|
||||||
|
expect(value2.__getValue()).toBe(42 / 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should stop tracking when animated', () => {
|
||||||
|
var value1 = new Animated.Value(0);
|
||||||
|
var value2 = new Animated.Value(0);
|
||||||
|
Animated.timing(value2, {
|
||||||
|
toValue: value1,
|
||||||
|
duration: 0,
|
||||||
|
}).start();
|
||||||
|
value1.setValue(42);
|
||||||
|
expect(value2.__getValue()).toBe(42);
|
||||||
|
Animated.timing(value2, {
|
||||||
|
toValue: 7,
|
||||||
|
duration: 0,
|
||||||
|
}).start();
|
||||||
|
value1.setValue(1492);
|
||||||
|
expect(value2.__getValue()).toBe(7);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Animated Vectors', () => {
|
||||||
|
it('should animate vectors', () => {
|
||||||
|
var vec = new Animated.ValueXY();
|
||||||
|
|
||||||
|
var callback = jest.genMockFunction();
|
||||||
|
|
||||||
|
var node = new Animated.__PropsOnlyForTests({
|
||||||
|
style: {
|
||||||
|
opacity: vec.x.interpolate({
|
||||||
|
inputRange: [0, 42],
|
||||||
|
outputRange: [0.2, 0.8],
|
||||||
|
}),
|
||||||
|
transform: vec.getTranslateTransform(),
|
||||||
|
...vec.getLayout(),
|
||||||
|
}
|
||||||
|
}, callback);
|
||||||
|
|
||||||
|
expect(node.__getValue()).toEqual({
|
||||||
|
style: {
|
||||||
|
opacity: 0.2,
|
||||||
|
transform: [
|
||||||
|
{translateX: 0},
|
||||||
|
{translateY: 0},
|
||||||
|
],
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
vec.setValue({x: 42, y: 1492});
|
||||||
|
|
||||||
|
expect(callback.mock.calls.length).toBe(2); // once each for x, y
|
||||||
|
|
||||||
|
expect(node.__getValue()).toEqual({
|
||||||
|
style: {
|
||||||
|
opacity: 0.8,
|
||||||
|
transform: [
|
||||||
|
{translateX: 42},
|
||||||
|
{translateY: 1492},
|
||||||
|
],
|
||||||
|
left: 42,
|
||||||
|
top: 1492,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
node.detach();
|
||||||
|
|
||||||
|
vec.setValue({x: 1, y: 1});
|
||||||
|
expect(callback.mock.calls.length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should track vectors', () => {
|
||||||
|
var value1 = new Animated.ValueXY();
|
||||||
|
var value2 = new Animated.ValueXY();
|
||||||
|
Animated.timing(value2, {
|
||||||
|
toValue: value1,
|
||||||
|
duration: 0,
|
||||||
|
}).start();
|
||||||
|
value1.setValue({x: 42, y: 1492});
|
||||||
|
expect(value2.__getValue()).toEqual({x: 42, y: 1492});
|
||||||
|
|
||||||
|
// Make sure tracking keeps working (see stopTogether in ParallelConfig used
|
||||||
|
// by maybeVectorAnim).
|
||||||
|
value1.setValue({x: 3, y: 4});
|
||||||
|
expect(value2.__getValue()).toEqual({x: 3, y: 4});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Animated Listeners', () => {
|
||||||
|
it('should get updates', () => {
|
||||||
|
var value1 = new Animated.Value(0);
|
||||||
|
var listener = jest.genMockFunction();
|
||||||
|
var id = value1.addListener(listener);
|
||||||
|
value1.setValue(42);
|
||||||
|
expect(listener.mock.calls.length).toBe(1);
|
||||||
|
expect(listener).toBeCalledWith({value: 42});
|
||||||
|
expect(value1.__getValue()).toBe(42);
|
||||||
|
value1.setValue(7);
|
||||||
|
expect(listener.mock.calls.length).toBe(2);
|
||||||
|
expect(listener).toBeCalledWith({value: 7});
|
||||||
|
expect(value1.__getValue()).toBe(7);
|
||||||
|
value1.removeListener(id);
|
||||||
|
value1.setValue(1492);
|
||||||
|
expect(listener.mock.calls.length).toBe(2);
|
||||||
|
expect(value1.__getValue()).toBe(1492);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should removeAll', () => {
|
||||||
|
var value1 = new Animated.Value(0);
|
||||||
|
var listener = jest.genMockFunction();
|
||||||
|
[1,2,3,4].forEach(() => value1.addListener(listener));
|
||||||
|
value1.setValue(42);
|
||||||
|
expect(listener.mock.calls.length).toBe(4);
|
||||||
|
expect(listener).toBeCalledWith({value: 42});
|
||||||
|
value1.removeAllListeners();
|
||||||
|
value1.setValue(7);
|
||||||
|
expect(listener.mock.calls.length).toBe(4);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,119 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
jest.dontMock('Easing');
|
||||||
|
|
||||||
|
var Easing = require('Easing');
|
||||||
|
describe('Easing', () => {
|
||||||
|
it('should work with linear', () => {
|
||||||
|
var easing = Easing.linear;
|
||||||
|
|
||||||
|
expect(easing(0)).toBe(0);
|
||||||
|
expect(easing(0.5)).toBe(0.5);
|
||||||
|
expect(easing(0.8)).toBe(0.8);
|
||||||
|
expect(easing(1)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with ease in linear', () => {
|
||||||
|
var easing = Easing.in(Easing.linear);
|
||||||
|
expect(easing(0)).toBe(0);
|
||||||
|
expect(easing(0.5)).toBe(0.5);
|
||||||
|
expect(easing(0.8)).toBe(0.8);
|
||||||
|
expect(easing(1)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with easy out linear', () => {
|
||||||
|
var easing = Easing.out(Easing.linear);
|
||||||
|
expect(easing(0)).toBe(0);
|
||||||
|
expect(easing(0.5)).toBe(0.5);
|
||||||
|
expect(easing(0.6)).toBe(0.6);
|
||||||
|
expect(easing(1)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with ease in quad', () => {
|
||||||
|
function easeInQuad(t) {
|
||||||
|
return t * t;
|
||||||
|
}
|
||||||
|
var easing = Easing.in(Easing.quad);
|
||||||
|
for (var t = -0.5; t < 1.5; t += 0.1) {
|
||||||
|
expect(easing(t)).toBe(easeInQuad(t));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with ease out quad', () => {
|
||||||
|
function easeOutQuad(t) {
|
||||||
|
return -t * (t - 2);
|
||||||
|
}
|
||||||
|
var easing = Easing.out(Easing.quad);
|
||||||
|
for (var t = 0; t <= 1; t += 0.1) {
|
||||||
|
expect(easing(1)).toBe(easeOutQuad(1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with ease in-out quad', () => {
|
||||||
|
function easeInOutQuad(t) {
|
||||||
|
t = t * 2;
|
||||||
|
if (t < 1) {
|
||||||
|
return 0.5 * t * t;
|
||||||
|
}
|
||||||
|
return -((t - 1) * (t - 3) - 1) / 2;
|
||||||
|
}
|
||||||
|
var easing = Easing.inOut(Easing.quad);
|
||||||
|
for (var t = -0.5; t < 1.5; t += 0.1) {
|
||||||
|
expect(easing(t)).toBeCloseTo(easeInOutQuad(t), 4);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function sampleEasingFunction(easing) {
|
||||||
|
var DURATION = 300;
|
||||||
|
var tickCount = Math.round(DURATION * 60 / 1000);
|
||||||
|
var samples = [];
|
||||||
|
for (var i = 0; i <= tickCount; i++) {
|
||||||
|
samples.push(easing(i / tickCount));
|
||||||
|
}
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
var Samples = {
|
||||||
|
in_quad: [0,0.0030864197530864196,0.012345679012345678,0.027777777777777776,0.04938271604938271,0.0771604938271605,0.1111111111111111,0.15123456790123457,0.19753086419753085,0.25,0.308641975308642,0.37345679012345684,0.4444444444444444,0.5216049382716049,0.6049382716049383,0.6944444444444445,0.7901234567901234,0.8919753086419753,1],
|
||||||
|
out_quad: [0,0.10802469135802469,0.20987654320987653,0.3055555555555555,0.3950617283950617,0.47839506172839513,0.5555555555555556,0.6265432098765432,0.691358024691358,0.75,0.8024691358024691,0.8487654320987654,0.888888888888889,0.9228395061728394,0.9506172839506174,0.9722222222222221,0.9876543209876543,0.9969135802469136,1],
|
||||||
|
inOut_quad: [0,0.006172839506172839,0.024691358024691357,0.05555555555555555,0.09876543209876543,0.154320987654321,0.2222222222222222,0.30246913580246915,0.3950617283950617,0.5,0.6049382716049383,0.697530864197531,0.7777777777777777,0.845679012345679,0.9012345679012346,0.9444444444444444,0.9753086419753086,0.9938271604938271,1],
|
||||||
|
in_cubic: [0,0.00017146776406035664,0.0013717421124828531,0.004629629629629629,0.010973936899862825,0.021433470507544586,0.037037037037037035,0.05881344307270234,0.0877914951989026,0.125,0.1714677640603567,0.22822359396433475,0.2962962962962963,0.37671467764060357,0.4705075445816187,0.5787037037037038,0.7023319615912208,0.8424211248285322,1],
|
||||||
|
out_cubic: [0,0.15757887517146785,0.2976680384087792,0.42129629629629617,0.5294924554183813,0.6232853223593964,0.7037037037037036,0.7717764060356652,0.8285322359396433,0.875,0.9122085048010974,0.9411865569272977,0.9629629629629629,0.9785665294924554,0.9890260631001372,0.9953703703703703,0.9986282578875172,0.9998285322359396,1],
|
||||||
|
inOut_cubic: [0,0.0006858710562414266,0.0054869684499314125,0.018518518518518517,0.0438957475994513,0.08573388203017834,0.14814814814814814,0.23525377229080935,0.3511659807956104,0.5,0.6488340192043895,0.7647462277091908,0.8518518518518519,0.9142661179698217,0.9561042524005487,0.9814814814814815,0.9945130315500685,0.9993141289437586,1],
|
||||||
|
in_sin: [0,0.003805301908254455,0.01519224698779198,0.03407417371093169,0.06030737921409157,0.09369221296335006,0.1339745962155613,0.1808479557110082,0.233955556881022,0.2928932188134524,0.35721239031346064,0.42642356364895384,0.4999999999999999,0.5773817382593005,0.6579798566743311,0.7411809548974793,0.8263518223330696,0.9128442572523416,0.9999999999999999],
|
||||||
|
out_sin: [0,0.08715574274765817,0.17364817766693033,0.25881904510252074,0.3420201433256687,0.42261826174069944,0.49999999999999994,0.573576436351046,0.6427876096865393,0.7071067811865475,0.766044443118978,0.8191520442889918,0.8660254037844386,0.9063077870366499,0.9396926207859083,0.9659258262890683,0.984807753012208,0.9961946980917455,1],
|
||||||
|
inOut_sin: [0,0.00759612349389599,0.030153689607045786,0.06698729810778065,0.116977778440511,0.17860619515673032,0.24999999999999994,0.32898992833716556,0.4131759111665348,0.49999999999999994,0.5868240888334652,0.6710100716628343,0.7499999999999999,0.8213938048432696,0.883022221559489,0.9330127018922194,0.9698463103929542,0.9924038765061041,1],
|
||||||
|
in_exp: [0,0.0014352875901128893,0.002109491677524035,0.0031003926796253885,0.004556754060844206,0.006697218616039631,0.009843133202303688,0.014466792379488908,0.021262343752724643,0.03125,0.045929202883612456,0.06750373368076916,0.09921256574801243,0.1458161299470146,0.2143109957132682,0.31498026247371835,0.46293735614364506,0.6803950000871883,1],
|
||||||
|
out_exp: [0,0.31960499991281155,0.5370626438563548,0.6850197375262816,0.7856890042867318,0.8541838700529854,0.9007874342519875,0.9324962663192309,0.9540707971163875,0.96875,0.9787376562472754,0.9855332076205111,0.9901568667976963,0.9933027813839603,0.9954432459391558,0.9968996073203746,0.9978905083224759,0.9985647124098871,1],
|
||||||
|
inOut_exp: [0,0.0010547458387620175,0.002278377030422103,0.004921566601151844,0.010631171876362321,0.022964601441806228,0.049606282874006216,0.1071554978566341,0.23146867807182253,0.5,0.7685313219281775,0.892844502143366,0.9503937171259937,0.9770353985581938,0.9893688281236377,0.9950784333988482,0.9977216229695779,0.998945254161238,1],
|
||||||
|
in_circle: [0,0.0015444024660317135,0.006192010000093506,0.013986702816730645,0.025003956956430873,0.03935464078941209,0.057190958417936644,0.07871533601238889,0.10419358352238339,0.1339745962155614,0.1685205807169019,0.20845517506805522,0.2546440075000701,0.3083389112228482,0.37146063894529113,0.4472292016074334,0.5418771527091488,0.6713289009389102,1],
|
||||||
|
out_circle: [0,0.3286710990610898,0.45812284729085123,0.5527707983925666,0.6285393610547089,0.6916610887771518,0.7453559924999298,0.7915448249319448,0.8314794192830981,0.8660254037844386,0.8958064164776166,0.9212846639876111,0.9428090415820634,0.9606453592105879,0.9749960430435691,0.9860132971832694,0.9938079899999065,0.9984555975339683,1],
|
||||||
|
inOut_circle: [0,0.003096005000046753,0.012501978478215436,0.028595479208968322,0.052096791761191696,0.08426029035845095,0.12732200375003505,0.18573031947264557,0.2709385763545744,0.5,0.7290614236454256,0.8142696805273546,0.8726779962499649,0.915739709641549,0.9479032082388084,0.9714045207910317,0.9874980215217846,0.9969039949999532,1],
|
||||||
|
in_back_: [0,-0.004788556241426612,-0.017301289437585736,-0.0347587962962963,-0.05438167352537723,-0.07339051783264748,-0.08900592592592595,-0.09844849451303156,-0.0989388203017833,-0.08769750000000004,-0.06194513031550073,-0.018902307956104283,0.044210370370370254,0.13017230795610413,0.2417629080932785,0.3817615740740742,0.5529477091906719,0.7581007167352535,0.9999999999999998],
|
||||||
|
out_back_: [2.220446049250313e-16,0.24189928326474652,0.44705229080932807,0.6182384259259258,0.7582370919067215,0.8698276920438959,0.9557896296296297,1.0189023079561044,1.0619451303155008,1.0876975,1.0989388203017834,1.0984484945130315,1.089005925925926,1.0733905178326475,1.0543816735253773,1.0347587962962963,1.0173012894375857,1.0047885562414267,1],
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keys(Samples).forEach(function(type) {
|
||||||
|
it('should ease ' + type, function() {
|
||||||
|
var [modeName, easingName, isFunction] = type.split('_');
|
||||||
|
var easing = Easing[easingName];
|
||||||
|
if (isFunction !== undefined) {
|
||||||
|
easing = easing();
|
||||||
|
}
|
||||||
|
var computed = sampleEasingFunction(Easing[modeName](easing));
|
||||||
|
var samples = Samples[type];
|
||||||
|
|
||||||
|
computed.forEach((value, key) => {
|
||||||
|
expect(value).toBeCloseTo(samples[key], 2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,256 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
jest
|
||||||
|
.dontMock('Interpolation')
|
||||||
|
.dontMock('Easing');
|
||||||
|
|
||||||
|
var Interpolation = require('Interpolation');
|
||||||
|
var Easing = require('Easing');
|
||||||
|
|
||||||
|
describe('Interpolation', () => {
|
||||||
|
it('should work with defaults', () => {
|
||||||
|
var interpolation = Interpolation.create({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [0, 1],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(interpolation(0)).toBe(0);
|
||||||
|
expect(interpolation(0.5)).toBe(0.5);
|
||||||
|
expect(interpolation(0.8)).toBe(0.8);
|
||||||
|
expect(interpolation(1)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with output range', () => {
|
||||||
|
var interpolation = Interpolation.create({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [100, 200],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(interpolation(0)).toBe(100);
|
||||||
|
expect(interpolation(0.5)).toBe(150);
|
||||||
|
expect(interpolation(0.8)).toBe(180);
|
||||||
|
expect(interpolation(1)).toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with input range', () => {
|
||||||
|
var interpolation = Interpolation.create({
|
||||||
|
inputRange: [100, 200],
|
||||||
|
outputRange: [0, 1],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(interpolation(100)).toBe(0);
|
||||||
|
expect(interpolation(150)).toBe(0.5);
|
||||||
|
expect(interpolation(180)).toBe(0.8);
|
||||||
|
expect(interpolation(200)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw for non monotonic input ranges', () => {
|
||||||
|
expect(() => Interpolation.create({
|
||||||
|
inputRange: [0, 2, 1],
|
||||||
|
outputRange: [0, 1, 2],
|
||||||
|
})).toThrow();
|
||||||
|
|
||||||
|
expect(() => Interpolation.create({
|
||||||
|
inputRange: [0, 1, 2],
|
||||||
|
outputRange: [0, 3, 1],
|
||||||
|
})).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with empty input range', () => {
|
||||||
|
var interpolation = Interpolation.create({
|
||||||
|
inputRange: [0, 10, 10],
|
||||||
|
outputRange: [1, 2, 3],
|
||||||
|
extrapolate: 'extend',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(interpolation(0)).toBe(1);
|
||||||
|
expect(interpolation(5)).toBe(1.5);
|
||||||
|
expect(interpolation(10)).toBe(2);
|
||||||
|
expect(interpolation(10.1)).toBe(3);
|
||||||
|
expect(interpolation(15)).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with empty output range', () => {
|
||||||
|
var interpolation = Interpolation.create({
|
||||||
|
inputRange: [1, 2, 3],
|
||||||
|
outputRange: [0, 10, 10],
|
||||||
|
extrapolate: 'extend',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(interpolation(0)).toBe(-10);
|
||||||
|
expect(interpolation(1.5)).toBe(5);
|
||||||
|
expect(interpolation(2)).toBe(10);
|
||||||
|
expect(interpolation(2.5)).toBe(10);
|
||||||
|
expect(interpolation(3)).toBe(10);
|
||||||
|
expect(interpolation(4)).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with easing', () => {
|
||||||
|
var interpolation = Interpolation.create({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [0, 1],
|
||||||
|
easing: Easing.quad,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(interpolation(0)).toBe(0);
|
||||||
|
expect(interpolation(0.5)).toBe(0.25);
|
||||||
|
expect(interpolation(0.9)).toBe(0.81);
|
||||||
|
expect(interpolation(1)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with extrapolate', () => {
|
||||||
|
var interpolation = Interpolation.create({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [0, 1],
|
||||||
|
extrapolate: 'extend',
|
||||||
|
easing: Easing.quad,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(interpolation(-2)).toBe(4);
|
||||||
|
expect(interpolation(2)).toBe(4);
|
||||||
|
|
||||||
|
interpolation = Interpolation.create({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [0, 1],
|
||||||
|
extrapolate: 'clamp',
|
||||||
|
easing: Easing.quad,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(interpolation(-2)).toBe(0);
|
||||||
|
expect(interpolation(2)).toBe(1);
|
||||||
|
|
||||||
|
interpolation = Interpolation.create({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [0, 1],
|
||||||
|
extrapolate: 'identity',
|
||||||
|
easing: Easing.quad,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(interpolation(-2)).toBe(-2);
|
||||||
|
expect(interpolation(2)).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with keyframes with extrapolate', () => {
|
||||||
|
var interpolation = Interpolation.create({
|
||||||
|
inputRange: [0, 10, 100, 1000],
|
||||||
|
outputRange: [0, 5, 50, 500],
|
||||||
|
extrapolate: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(interpolation(-5)).toBe(-2.5);
|
||||||
|
expect(interpolation(0)).toBe(0);
|
||||||
|
expect(interpolation(5)).toBe(2.5);
|
||||||
|
expect(interpolation(10)).toBe(5);
|
||||||
|
expect(interpolation(50)).toBe(25);
|
||||||
|
expect(interpolation(100)).toBe(50);
|
||||||
|
expect(interpolation(500)).toBe(250);
|
||||||
|
expect(interpolation(1000)).toBe(500);
|
||||||
|
expect(interpolation(2000)).toBe(1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with keyframes without extrapolate', () => {
|
||||||
|
var interpolation = Interpolation.create({
|
||||||
|
inputRange: [0, 1, 2],
|
||||||
|
outputRange: [0.2, 1, 0.2],
|
||||||
|
extrapolate: 'clamp',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(interpolation(5)).toBeCloseTo(0.2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw for an infinite input range', () => {
|
||||||
|
expect(() => Interpolation.create({
|
||||||
|
inputRange: [-Infinity, Infinity],
|
||||||
|
outputRange: [0, 1],
|
||||||
|
})).toThrow();
|
||||||
|
|
||||||
|
expect(() => Interpolation.create({
|
||||||
|
inputRange: [-Infinity, 0, Infinity],
|
||||||
|
outputRange: [1, 2, 3],
|
||||||
|
})).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with negative infinite', () => {
|
||||||
|
var interpolation = Interpolation.create({
|
||||||
|
inputRange: [-Infinity, 0],
|
||||||
|
outputRange: [-Infinity, 0],
|
||||||
|
easing: Easing.quad,
|
||||||
|
extrapolate: 'identity',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(interpolation(-Infinity)).toBe(-Infinity);
|
||||||
|
expect(interpolation(-100)).toBeCloseTo(-10000);
|
||||||
|
expect(interpolation(-10)).toBeCloseTo(-100);
|
||||||
|
expect(interpolation(0)).toBeCloseTo(0);
|
||||||
|
expect(interpolation(1)).toBeCloseTo(1);
|
||||||
|
expect(interpolation(100)).toBeCloseTo(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with positive infinite', () => {
|
||||||
|
var interpolation = Interpolation.create({
|
||||||
|
inputRange: [5, Infinity],
|
||||||
|
outputRange: [5, Infinity],
|
||||||
|
easing: Easing.quad,
|
||||||
|
extrapolate: 'identity',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(interpolation(-100)).toBeCloseTo(-100);
|
||||||
|
expect(interpolation(-10)).toBeCloseTo(-10);
|
||||||
|
expect(interpolation(0)).toBeCloseTo(0);
|
||||||
|
expect(interpolation(5)).toBeCloseTo(5);
|
||||||
|
expect(interpolation(6)).toBeCloseTo(5 + 1);
|
||||||
|
expect(interpolation(10)).toBeCloseTo(5 + 25);
|
||||||
|
expect(interpolation(100)).toBeCloseTo(5 + (95 * 95));
|
||||||
|
expect(interpolation(Infinity)).toBe(Infinity);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with output ranges as string', () => {
|
||||||
|
var interpolation = Interpolation.create({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)'],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(interpolation(0)).toBe('rgba(0, 100, 200, 0)');
|
||||||
|
expect(interpolation(0.5)).toBe('rgba(25, 125, 225, 0.25)');
|
||||||
|
expect(interpolation(1)).toBe('rgba(50, 150, 250, 0.5)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with negative and decimal values in string ranges', () => {
|
||||||
|
var interpolation = Interpolation.create({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: ['-100.5deg', '100deg'],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(interpolation(0)).toBe('-100.5deg');
|
||||||
|
expect(interpolation(0.5)).toBe('-0.25deg');
|
||||||
|
expect(interpolation(1)).toBe('100deg');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should crash when chaining an interpolation that returns a string', () => {
|
||||||
|
var interpolation = Interpolation.create({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [0, 1],
|
||||||
|
});
|
||||||
|
expect(() => { interpolation('45rad'); }).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should crash when defining output range with different pattern', () => {
|
||||||
|
expect(() => Interpolation.create({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: ['rgba(0, 100, 200, 0)', 'rgb(50, 150, 250)'],
|
||||||
|
})).toThrow();
|
||||||
|
|
||||||
|
expect(() => Interpolation.create({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: ['20deg', '30rad'],
|
||||||
|
})).toThrow();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"name": "react-animated",
|
||||||
|
"description": "Animated provides powerful mechanisms for animating your React views",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"keywords": [
|
||||||
|
"react",
|
||||||
|
"animated",
|
||||||
|
"animation"
|
||||||
|
],
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"main": "Animated.js",
|
||||||
|
"readmeFilename": "README.md"
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
/**
|
||||||
|
* https://github.com/arian/cubic-bezier
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013 Arian Stolwijk
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @providesModule bezier
|
||||||
|
* @nolint
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = function(x1, y1, x2, y2, epsilon){
|
||||||
|
|
||||||
|
var curveX = function(t){
|
||||||
|
var v = 1 - t;
|
||||||
|
return 3 * v * v * t * x1 + 3 * v * t * t * x2 + t * t * t;
|
||||||
|
};
|
||||||
|
|
||||||
|
var curveY = function(t){
|
||||||
|
var v = 1 - t;
|
||||||
|
return 3 * v * v * t * y1 + 3 * v * t * t * y2 + t * t * t;
|
||||||
|
};
|
||||||
|
|
||||||
|
var derivativeCurveX = function(t){
|
||||||
|
var v = 1 - t;
|
||||||
|
return 3 * (2 * (t - 1) * t + v * v) * x1 + 3 * (- t * t * t + 2 * v * t) * x2;
|
||||||
|
};
|
||||||
|
|
||||||
|
return function(t){
|
||||||
|
|
||||||
|
var x = t, t0, t1, t2, x2, d2, i;
|
||||||
|
|
||||||
|
// First try a few iterations of Newton's method -- normally very fast.
|
||||||
|
for (t2 = x, i = 0; i < 8; i++){
|
||||||
|
x2 = curveX(t2) - x;
|
||||||
|
if (Math.abs(x2) < epsilon) return curveY(t2);
|
||||||
|
d2 = derivativeCurveX(t2);
|
||||||
|
if (Math.abs(d2) < 1e-6) break;
|
||||||
|
t2 = t2 - x2 / d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
t0 = 0, t1 = 1, t2 = x;
|
||||||
|
|
||||||
|
if (t2 < t0) return curveY(t0);
|
||||||
|
if (t2 > t1) return curveY(t1);
|
||||||
|
|
||||||
|
// Fallback to the bisection method for reliability.
|
||||||
|
while (t0 < t1){
|
||||||
|
x2 = curveX(t2);
|
||||||
|
if (Math.abs(x2 - x) < epsilon) return curveY(t2);
|
||||||
|
if (x > x2) t0 = t2;
|
||||||
|
else t1 = t2;
|
||||||
|
t2 = (t1 - t0) * .5 + t0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failure
|
||||||
|
return curveY(t2);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
|
@ -45,6 +45,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
|
||||||
ActionSheetIOS: require('ActionSheetIOS'),
|
ActionSheetIOS: require('ActionSheetIOS'),
|
||||||
AdSupportIOS: require('AdSupportIOS'),
|
AdSupportIOS: require('AdSupportIOS'),
|
||||||
AlertIOS: require('AlertIOS'),
|
AlertIOS: require('AlertIOS'),
|
||||||
|
Animated: require('Animated'),
|
||||||
AppRegistry: require('AppRegistry'),
|
AppRegistry: require('AppRegistry'),
|
||||||
AppStateIOS: require('AppStateIOS'),
|
AppStateIOS: require('AppStateIOS'),
|
||||||
AsyncStorage: require('AsyncStorage'),
|
AsyncStorage: require('AsyncStorage'),
|
||||||
|
|
Загрузка…
Ссылка в новой задаче