1 | import React, { PropTypes, Component } from 'react';
|
2 | import { View, Animated, Easing, PanResponder } from 'react-native'
|
3 | import { styles, radius } from './styles.js';
|
4 |
|
5 | export default class Ripple extends Component {
|
6 | static defaultProps = {
|
7 | rippleColor: 'black',
|
8 | rippleOpacity: 0.20,
|
9 | rippleDuration: 400,
|
10 | rippleSize: 0,
|
11 | rippleContainerBorderRadius: 0,
|
12 | };
|
13 |
|
14 | static propTypes = {
|
15 | rippleColor: PropTypes.string,
|
16 | rippleOpacity: PropTypes.number,
|
17 | rippleDuration: PropTypes.number,
|
18 | rippleSize: PropTypes.number,
|
19 | rippleContainerBorderRadius: PropTypes.number,
|
20 | };
|
21 |
|
22 | constructor(props) {
|
23 | super(props);
|
24 |
|
25 | this.unique = 0;
|
26 |
|
27 | this.state = {
|
28 | size: 0,
|
29 | ripples: [],
|
30 | };
|
31 | }
|
32 |
|
33 | componentWillMount() {
|
34 | this.panResponder = PanResponder.create({
|
35 | onStartShouldSetPanResponder: () => {
|
36 | return true;
|
37 | },
|
38 |
|
39 | onPanResponderGrant: (event, gestureState) => {
|
40 | let { onPressIn } = this.props;
|
41 |
|
42 | if (typeof onPressIn === 'function') {
|
43 | onPressIn();
|
44 | }
|
45 | },
|
46 |
|
47 | onPanResponderRelease: (event, gestureState) => {
|
48 | let { onPressOut } = this.props;
|
49 |
|
50 | if (typeof onPressOut === 'function') {
|
51 | onPressOut();
|
52 | }
|
53 |
|
54 | this.startRipple(event);
|
55 | }
|
56 | });
|
57 | }
|
58 |
|
59 | onLayout(event) {
|
60 | let { rippleSize } = this.props;
|
61 | let { width, height } = event.nativeEvent.layout;
|
62 |
|
63 | let size = rippleSize > 0?
|
64 | rippleSize : Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
|
65 |
|
66 | this.setState({ size });
|
67 | }
|
68 |
|
69 | startRipple(event) {
|
70 | let { locationX, locationY } = event.nativeEvent;
|
71 | let { rippleDuration, rippleOpacity } = this.props;
|
72 | let { ripples, size } = this.state;
|
73 |
|
74 | let unique = this.unique++;
|
75 |
|
76 | let ripple = {
|
77 | scale: new Animated.Value(1 / (radius * 2)),
|
78 | opacity: new Animated.Value(rippleOpacity),
|
79 |
|
80 | unique, locationX, locationY,
|
81 | };
|
82 |
|
83 | ripples.push(ripple);
|
84 |
|
85 | Animated
|
86 | .parallel([
|
87 | Animated.timing(ripple.scale, {
|
88 | toValue: size / (radius * 2),
|
89 | duration: rippleDuration,
|
90 | easing: Easing.out(Easing.ease),
|
91 | }),
|
92 |
|
93 | Animated.timing(ripple.opacity, {
|
94 | toValue: 0,
|
95 | duration: rippleDuration,
|
96 | easing: Easing.out(Easing.ease),
|
97 | }),
|
98 | ])
|
99 | .start(() => {
|
100 | let ripples = this.state.ripples.slice(1);
|
101 |
|
102 | this.setState({ ripples });
|
103 | });
|
104 |
|
105 | this.setState({ ripples });
|
106 | }
|
107 |
|
108 | render() {
|
109 | let { children, rippleColor, rippleContainerBorderRadius, ...props } = this.props;
|
110 | let { ripples } = this.state;
|
111 |
|
112 | ripples = ripples
|
113 | .map(($_) => {
|
114 | let { scale, opacity, unique } = $_;
|
115 |
|
116 | let style = {
|
117 | top: $_.locationY - radius,
|
118 | left: $_.locationX - radius,
|
119 | backgroundColor: rippleColor,
|
120 |
|
121 | transform: [{ scale }],
|
122 | opacity,
|
123 | };
|
124 |
|
125 | return (
|
126 | <Animated.View style={[ styles.ripple, style ]} key={unique} pointerEvents='none' />
|
127 | );
|
128 | });
|
129 |
|
130 | return (
|
131 | <Animated.View onLayout={this.onLayout.bind(this)} {...props} {...this.panResponder.panHandlers}>
|
132 | {children}
|
133 |
|
134 | <View style={[styles.container, { borderRadius: rippleContainerBorderRadius }]}>
|
135 | {ripples}
|
136 | </View>
|
137 | </Animated.View>
|
138 | );
|
139 | }
|
140 | }
|