UNPKG

4.28 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.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 this.focused = false;
27
28 this.state = {
29 size: 0,
30 width: 0,
31 height: 0,
32 ripples: [],
33 };
34 }
35
36 componentWillMount() {
37 this.panResponder = PanResponder.create({
38 onStartShouldSetPanResponder: () => true,
39 onMoveShouldSetPanResponder: () => true,
40 onPanResponderTerminationRequest: () => false,
41
42 onPanResponderGrant: (event, gestureState) => {
43 this.setFocused(true);
44 },
45
46 onPanResponderMove: (event, gestureState) => {
47 let { locationX, locationY } = event.nativeEvent;
48 let { width, height } = this.state;
49
50 let focused =
51 (locationX >= 0 && locationX <= width) &&
52 (locationY >= 0 && locationY <= height);
53
54 this.setFocused(focused);
55 },
56
57 onPanResponderRelease: (event, gestureState) => {
58 let { onPress } = this.props;
59
60 if (this.focused) {
61 this.startRipple(event);
62
63 if (typeof onPress === 'function') {
64 onPress();
65 }
66 }
67
68 this.setFocused(false);
69 },
70
71 onPanResponderTerminate: (event, gestureState) => {
72 this.setFocused(false);
73 },
74 });
75 }
76
77 setFocused(focused) {
78 if (focused ^ this.focused) {
79 this.focused = focused;
80 this.onFocusChage();
81 }
82 }
83
84 onFocusChage() {
85 let { onPressOut, onPressIn } = this.props;
86
87 if (this.focused) {
88 if (typeof onPressIn === 'function') {
89 onPressIn();
90 }
91 } else {
92 if (typeof onPressOut === 'function') {
93 onPressOut();
94 }
95 }
96 }
97
98 onLayout(event) {
99 let { rippleSize } = this.props;
100 let { width, height } = event.nativeEvent.layout;
101
102 let size = rippleSize > 0?
103 rippleSize : Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
104
105 this.setState({ size, width, height });
106 }
107
108 startRipple(event) {
109 let { locationX, locationY } = event.nativeEvent;
110 let { rippleDuration, rippleOpacity } = this.props;
111 let { ripples, size } = this.state;
112
113 let unique = this.unique++;
114
115 let ripple = {
116 scale: new Animated.Value(1 / (radius * 2)),
117 opacity: new Animated.Value(rippleOpacity),
118
119 unique, locationX, locationY,
120 };
121
122 ripples.push(ripple);
123
124 Animated
125 .parallel([
126 Animated.timing(ripple.scale, {
127 toValue: size / (radius * 2),
128 duration: rippleDuration,
129 easing: Easing.out(Easing.ease),
130 }),
131
132 Animated.timing(ripple.opacity, {
133 toValue: 0,
134 duration: rippleDuration,
135 easing: Easing.out(Easing.ease),
136 }),
137 ])
138 .start(() => {
139 let ripples = this.state.ripples.slice(1);
140
141 this.setState({ ripples });
142 });
143
144 this.setState({ ripples });
145 }
146
147 render() {
148 let { children, rippleColor, rippleContainerBorderRadius, ...props } = this.props;
149 let { ripples } = this.state;
150
151 ripples = ripples
152 .map(($_) => {
153 let { scale, opacity, unique } = $_;
154
155 let style = {
156 top: $_.locationY - radius,
157 left: $_.locationX - radius,
158 backgroundColor: rippleColor,
159
160 transform: [{ scale }],
161 opacity,
162 };
163
164 return (
165 <Animated.View style={[ styles.ripple, style ]} key={unique} pointerEvents='none' />
166 );
167 });
168
169 return (
170 <Animated.View onLayout={this.onLayout.bind(this)} {...props} {...this.panResponder.panHandlers}>
171 {children}
172
173 <View style={[styles.container, { borderRadius: rippleContainerBorderRadius }]}>
174 {ripples}
175 </View>
176 </Animated.View>
177 );
178 }
179}