UNPKG

4.83 kBJavaScriptView Raw
1import React, { PropTypes, Component } from 'react';
2import { View, Animated, Easing, PanResponder } from 'react-native'
3import { styles, radius } from './styles.js';
4
5export default class Ripple extends Component {
6 static defaultProps = {
7 rippleColor: 'black',
8 rippleOpacity: 0.30,
9 rippleDuration: 400,
10 rippleSize: 0,
11 rippleContainerBorderRadius: 0,
12 rippleCentered: false,
13 disabled: false,
14 };
15
16 static propTypes = {
17 rippleColor: PropTypes.string,
18 rippleOpacity: PropTypes.number,
19 rippleDuration: PropTypes.number,
20 rippleSize: PropTypes.number,
21 rippleContainerBorderRadius: PropTypes.number,
22 rippleCentered: PropTypes.bool,
23 disabled: PropTypes.bool,
24 };
25
26 constructor(props) {
27 super(props);
28
29 this.onLayout = this.onLayout.bind(this);
30
31 this.unique = 0;
32 this.focused = false;
33
34 this.state = {
35 width: 0,
36 height: 0,
37 ripples: [],
38 };
39 }
40
41 componentWillMount() {
42 this.panResponder = PanResponder.create({
43 onShouldBlockNativeResponder: () => false,
44
45 onStartShouldSetPanResponder: () => !this.props.disabled,
46 onMoveShouldSetPanResponder: () => !this.props.disabled,
47
48 onPanResponderGrant: (event, gestureState) => {
49 this.setFocused(true);
50 },
51
52 onPanResponderMove: (event, gestureState) => {
53 let { locationX, locationY } = event.nativeEvent;
54 let { width, height } = this.state;
55
56 let focused =
57 (locationX >= 0 && locationX <= width) &&
58 (locationY >= 0 && locationY <= height);
59
60 this.setFocused(focused);
61 },
62
63 onPanResponderRelease: (event, gestureState) => {
64 let { onPress, disabled } = this.props;
65
66 if (this.focused && !disabled) {
67 this.startRipple(event);
68
69 if (typeof onPress === 'function') {
70 onPress();
71 }
72 }
73
74 this.setFocused(false);
75 },
76
77 onPanResponderTerminate: (event, gestureState) => {
78 this.setFocused(false);
79 },
80 });
81 }
82
83 setFocused(focused) {
84 if (focused ^ this.focused) {
85 this.onFocusChange(this.focused = focused);
86 }
87 }
88
89 onFocusChange(focused) {
90 let { onPressOut, onPressIn } = this.props;
91
92 if (focused) {
93 if (typeof onPressIn === 'function') {
94 onPressIn();
95 }
96 } else {
97 if (typeof onPressOut === 'function') {
98 onPressOut();
99 }
100 }
101 }
102
103 onLayout(event) {
104 let { rippleSize } = this.props;
105 let { width, height } = event.nativeEvent.layout;
106
107 this.setState({ width, height });
108 }
109
110 startRipple(event) {
111 let { rippleDuration, rippleOpacity, rippleCentered } = this.props;
112 let { rippleSize, rippleContainerBorderRadius: r } = this.props;
113 let { ripples, width, height } = this.state;
114
115 let w2 = 0.5 * width;
116 let h2 = 0.5 * height;
117
118 let { locationX, locationY } = rippleCentered?
119 { locationX: w2, locationY: h2 }:
120 event.nativeEvent;
121
122 let offsetX = Math.abs(w2 - locationX);
123 let offsetY = Math.abs(h2 - locationY);
124
125 let R = rippleSize > 0?
126 0.5 * rippleSize:
127 Math.sqrt(Math.pow(w2 + offsetX, 2) + Math.pow(h2 + offsetY, 2));
128
129 let unique = this.unique++;
130
131 let ripple = {
132 scale: new Animated.Value(0.5 / radius),
133 opacity: new Animated.Value(rippleOpacity),
134
135 unique, locationX, locationY,
136 };
137
138 ripples.push(ripple);
139
140 Animated
141 .parallel([
142 Animated.timing(ripple.scale, {
143 toValue: R / radius,
144 duration: rippleDuration,
145 easing: Easing.out(Easing.ease),
146 }),
147
148 Animated.timing(ripple.opacity, {
149 toValue: 0,
150 duration: rippleDuration,
151 easing: Easing.out(Easing.ease),
152 }),
153 ])
154 .start(() => {
155 let ripples = this.state.ripples.slice(1);
156
157 this.setState({ ripples });
158 });
159
160 this.setState({ ripples });
161 }
162
163 render() {
164 let { children, rippleColor, rippleContainerBorderRadius, ...props } = this.props;
165 let { ripples } = this.state;
166
167 let containerStyle = {
168 borderRadius: rippleContainerBorderRadius,
169 };
170
171 ripples = ripples
172 .map(({ scale, opacity, locationX, locationY, unique }) => {
173 let rippleStyle = {
174 top: locationY - radius,
175 left: locationX - radius,
176 backgroundColor: rippleColor,
177
178 transform: [{ scale }],
179 opacity,
180 };
181
182 return (
183 <Animated.View style={[ styles.ripple, rippleStyle ]} key={unique} pointerEvents='none' />
184 );
185 });
186
187 return (
188 <Animated.View onLayout={this.onLayout} {...props} {...this.panResponder.panHandlers}>
189 {children}
190
191 <View style={[ styles.container, containerStyle ]}>
192 {ripples}
193 </View>
194 </Animated.View>
195 );
196 }
197}