Skip to content

Commit

Permalink
Deprecate play() and swipeThreshold
Browse files Browse the repository at this point in the history
Breaking changes:
- play() is now deprecated, use goTo(index: Array<number>) instead
- swipeThreshold prop has been replaced with swipeVelocityThreshold and
swipeDistanceThreshold
  • Loading branch information
sonaye committed Jul 15, 2017
1 parent f6fa032 commit ea43ce0
Show file tree
Hide file tree
Showing 17 changed files with 80 additions and 97 deletions.
1 change: 0 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
demos/
examples/
33 changes: 18 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,37 @@
# react-native-behavior
<img src="https://raw.githubusercontent.com/sonaye/react-native-behavior/master/demos/demo1.gif" width="400">
<img src="https://raw.githubusercontent.com/sonaye/react-native-behavior/master/examples/demos/demo1.gif" width="400">

You define the behavior states of the component, and then animate between them.

```javascript
<Behavior
ref={ref => (this.box = ref)}
states={[
{ backgroundColor: 'gray', height: 100, width: 100 }, // state 0
{ backgroundColor: 'gray' }, // state 0
{ backgroundColor: 'green' }, // state 1
{ height: 150 }, // state 2
{ opacity: 0.5 }, // state 3
{ rotate: '45deg' }, // state 4
{ opacity: 0.5 }, // state 2
{ rotate: '45deg' }, // state 3
]}
/>

// ..

this.box.goTo(1);
this.box.play([2, 3, 4]);
this.box.goTo(1); // animates box's backgroundColor from gray to green
this.box.goTo(2); // animates the opacity of the -now- green box from 1 to 0.5
this.box.goTo(3); // rotates the faded green box 45 degrees, starting from 0

this.box.goTo([1, 2, 3]); // plays a sequence of behavior states, colorize then fade then tilt
```

More demos available [here](https://github.com/sonaye/react-native-behavior/tree/master/demos).
More demos available [here](https://github.com/sonaye/react-native-behavior/tree/master/examples/demos).

# Installation
`yarn add react-native-behavior`

# Definition
```javascript
type behavior = {
config?: { // goTo() and play() default configuration
config?: { // goTo() default configuration
mode?: 'spring' | 'timing', // default = 'spring'
callback?: Function, // to be executed after animating to a new state
...AnimatedSpringOptions, // excluding toValue, useNativeDriver (see React Native docs)
Expand All @@ -46,25 +48,26 @@ type behavior = {
translateY?: number, // default = 0
width?: number, // no percentages, default = null
}>, // minimum two states required
style?: Object, // default = {}
style?: Object, // default = {}, AnimatedViewStyle (see React Native docs)
enableGestures?: boolean, // simple swipe up/down/left/right and pressed/long pressed
onGesture?: Function, // e.g. gesture => console.log(gesture)
indices?: Array<number>, // required on android to avoid a glitch
// indices can also be used with custom drivers to define custom state keys/values
clamp?: boolean, // default = false, prevent animations from exceeding their ranges
swipeThreshold?: { velocity?: number, distance?: number }, // default = { velocity: 0.3, distance: 10 }
swipeVelocityThreshold?: number, // default = 0.3
swipeDistanceThreshold?: number, // default = 10
animatedNativeValue?: AnimatedValue, // default = new Animated.Value(0), use a custom native driver
animatedValue?: AnimatedValue // default = new Animated.Value(0), use a custom driver
animatedValue?: AnimatedValue, // default = new Animated.Value(0), use a custom driver
// animatedNativeValue and animatedValue should be used together, different instances of Animated.Value
// animatedNativeValue is needed for opacity, rotate, scale, translateX and translateY
// animatedValue is needed for backgroundColor, height and width
children?: any // the behavior component can enclose other components, can enclose another behavior too
};

// methods
behavior.goTo(index: number, config?: Object = {}) // animate to a specific behavior state
behavior.play(indices: Array<number>, config?: Object = {}) // animate a sequence of behavior states
behavior.goTo(index: number | Array<number>, config?: Object = {}) // animate to a specific behavior state

// to retrieve current state index you can directly use behavior.index
behavior.index // to retrieve current state index
```

## Examples
Expand Down
127 changes: 56 additions & 71 deletions behavior.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,52 @@ import React, { Component } from 'react';
import { Animated, PanResponder, TouchableOpacity } from 'react-native';

export default class extends Component {
index = this.props.initialState || 0;
static defaultProps = {
config: { mode: 'spring' },
initialState: 0,
style: {},
enableGestures: false,
clamp: false,
swipeVelocityThreshold: 0.3,
swipeDistanceThreshold: 10
};

nativeValue = this.props.animatedNativeValue || new Animated.Value(0);
value = this.props.animatedValue || new Animated.Value(0);

goTo = (toValue, config = {}) => {
index = this.props.initialState;

goTo = (value, config = {}) => {
const { mode, callback, ...options } = {
...this.props.config,
...config
};

const animate = mode === 'timing' ? Animated.timing : Animated.spring;
const curve = mode === 'timing' ? Animated.timing : Animated.spring;

Animated.parallel([
animate(this.nativeValue, { ...options, toValue, useNativeDriver: true }),
animate(this.value, { ...options, toValue })
]).start(animation => {
if (animation.finished && callback) callback();
});
const animate = toValue =>
Animated.parallel([
curve(this.nativeValue, { ...options, toValue, useNativeDriver: true }),
curve(this.value, { ...options, toValue })
]);

this.index = toValue;
};
if (Array.isArray(value)) {
const states = [];

play = (values, config = {}) => {
const { mode, callback, ...options } = {
...this.props.config,
...config
};
value.forEach(toValue => states.push(animate(toValue)));

const animate = mode === 'timing' ? Animated.timing : Animated.spring;

const states = [];

values.forEach(toValue =>
states.push(
Animated.parallel([
animate(this.nativeValue, {
...options,
toValue,
useNativeDriver: true
}),
animate(this.value, { ...options, toValue })
])
)
);
Animated.sequence(states).start(animation => {
if (animation.finished && callback) callback();
});

Animated.sequence(states).start(animation => {
if (animation.finished && callback) callback();
});
this.index = states[states.length - 1];
} else {
animate(value).start(animation => {
if (animation.finished && callback) callback();
});

this.index = values[values.length - 1];
this.index = value;
}
};

render() {
Expand All @@ -66,25 +61,14 @@ export default class extends Component {
initialState,
onGesture,
states,
swipeThreshold
style,
swipeVelocityThreshold,
swipeDistanceThreshold
} = this.props;

const style = this.props.style || {};

const inputRange = indices || [...Array(states.length).keys()];

const defaultState = {
backgroundColor: 'transparent',
height: null,
opacity: 1,
rotate: '0deg',
scale: 1,
translateX: 0,
translateY: 0,
width: null
};

const getRange = prop =>
const getRange = (prop, defaultValue) =>
states.reduce((range, state, i) => {
const prevState = range[i - 1];

Expand All @@ -93,37 +77,35 @@ export default class extends Component {
? state[prop]
: prevState || prevState === 0
? prevState
: style[prop] || style[prop] === 0
? style[prop]
: defaultState[prop]
: style[prop] || style[prop] === 0 ? style[prop] : defaultValue
);

return range;
}, []);

const addNativeProp = prop =>
const addNativeProp = (prop, defaultValue) =>
nativeValue.interpolate({
inputRange,
outputRange: getRange(prop),
outputRange: getRange(prop, defaultValue),
extrapolate: clamp ? 'clamp' : null
});

const addProp = prop =>
const addProp = (prop, defaultValue) =>
value.interpolate({
inputRange,
outputRange: getRange(prop),
outputRange: getRange(prop, defaultValue),
extrapolate: clamp ? 'clamp' : null
});

const opacity = addNativeProp('opacity');
const rotate = addNativeProp('rotate');
const scale = addNativeProp('scale');
const translateX = addNativeProp('translateX');
const translateY = addNativeProp('translateY');
const opacity = addNativeProp('opacity', 1);
const rotate = addNativeProp('rotate', '0deg');
const scale = addNativeProp('scale', 1);
const translateX = addNativeProp('translateX', 0);
const translateY = addNativeProp('translateY', 0);

const backgroundColor = addProp('backgroundColor');
const height = addProp('height');
const width = addProp('width');
const backgroundColor = addProp('backgroundColor', 'transparent');
const height = addProp('height', null);
const width = addProp('width', null);

const nativeStyles = {
opacity,
Expand All @@ -142,19 +124,22 @@ export default class extends Component {
let swipeVelocity = null;
let swipeDistance = null;

const velocityThr = (swipeThreshold && swipeThreshold.velocity) || 0.3;
const distanceThr = (swipeThreshold && swipeThreshold.distance) || 10;

this.pan = PanResponder.create({
onMoveShouldSetPanResponder: e => e.nativeEvent.touches.length === 1,
onPanResponderMove: (e, { dx, dy, vx, vy }) => {
if (Math.abs(vx) > velocityThr && Math.abs(dy) < distanceThr) {
if (
Math.abs(vx) > swipeVelocityThreshold &&
Math.abs(dy) < swipeDistanceThreshold
) {
swipeVelocity = vx;
swipeDistance = dx;

if (dx < 0) swiped = 'left';
else if (dx > 0) swiped = 'right';
} else if (Math.abs(vy) > velocityThr && Math.abs(dx) < distanceThr) {
} else if (
Math.abs(vy) > swipeVelocityThreshold &&
Math.abs(dx) < swipeDistanceThreshold
) {
swipeVelocity = vy;
swipeDistance = dy;

Expand Down
10 changes: 3 additions & 7 deletions examples/box.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,13 @@ import Behavior from '../behavior';

const Example = () => {
let goTo;
let play;

return (
<View style={{ flex: 1 }}>
<View style={{ alignItems: 'center', flex: 1, justifyContent: 'center' }}>
<Behavior
config={{ mode: 'timing', duration: 250 }}
ref={ref => {
goTo = ref.goTo;
play = ref.play;
}}
ref={ref => (goTo = ref.goTo)}
states={[
{ backgroundColor: '#d8d8d8', height: 100, width: 100 },
{ backgroundColor: '#0f9d58' },
Expand Down Expand Up @@ -57,12 +53,12 @@ const Example = () => {
<Button
color="#d8d8d8"
title="play"
onPress={() => play([1, 2, 3, 4, 5, 6, 7, 8])}
onPress={() => goTo([1, 2, 3, 4, 5, 6, 7, 8])}
/>
<Button
color="#d8d8d8"
title="reset"
onPress={() => play([7, 6, 5, 4, 3, 2, 1, 0])}
onPress={() => goTo([7, 6, 5, 4, 3, 2, 1, 0])}
/>
</View>
</View>
Expand Down
2 changes: 1 addition & 1 deletion examples/containerWithGestures.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const Example = () =>
else this.container.goTo(0);
}}
indices={[0, 1, 2]} // android only
swipeThreshold={{ distance: 40 }}
swipeDistanceThreshold={40}
/>
</View>;

Expand Down
File renamed without changes.
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
2 changes: 1 addition & 1 deletion examples/sequence.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const Example = () => {
config={{ mode: 'timing', delay: i * 250 }}
indices={[0, 1, 2, 3, 4]}
key={i}
ref={ref => ref.play([1, 2, 3, 4])}
ref={ref => ref.goTo([1, 2, 3, 4])}
states={[
{ translateX: -width, opacity: 0 },
{ translateX: 0, opacity: 1 },
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "react-native-behavior",
"description": "Easily create stateful animations in React Native.",
"version": "0.0.17",
"version": "0.0.18",
"main": "behavior.js",
"repository": {
"type": "git",
Expand Down

0 comments on commit ea43ce0

Please sign in to comment.