mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-02-10 09:12:46 +08:00
[ReactNative] Move Animated to Open Source
Summary: Moves the files over and Exports Animated from 'react-native'.
This commit is contained in:
319
Examples/UIExplorer/AnimationExample/AnExApp.js
Normal file
319
Examples/UIExplorer/AnimationExample/AnExApp.js
Normal file
@@ -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;
|
||||
Reference in New Issue
Block a user