Skip to content

Instantly share code, notes, and snippets.

@ryohlan
Last active March 20, 2018 09:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ryohlan/e29743ae1d2f01e18499ec7073766b35 to your computer and use it in GitHub Desktop.
Save ryohlan/e29743ae1d2f01e18499ec7073766b35 to your computer and use it in GitHub Desktop.
マテリアルデザインのRippleエフェクトをReact Nativeで実装してみたよ
// @flow
import React from 'react';
import { View, Animated, TouchableOpacity, PanResponder } from 'react-native';
export type Props = {
children: any,
style: any,
onPress: Function
};
type State = {
circlePosition: { x: number, y: number },
scaleAnimated: any,
opacityAnimated: any,
rippleRadius: number,
};
class MaterialButton extends React.Component<void, Props, State>{
panResponder: any = null
state: State = {
circlePosition: { x: 0, y: 0 },
scaleAnimated: new Animated.Value(0),
opacityAnimated: new Animated.Value(0),
rippleRadius: 0,
}
startScaleAnimation() {
this.resetAnimation();
Animated.timing(this.state.scaleAnimated, {
duration: 800,
toValue: 1,
}).start();
}
startOpacityAnimation() {
Animated.timing(this.state.opacityAnimated, {
duration: 600,
toValue: 1,
}).start(() => this.resetAnimation());
}
resetAnimation() {
this.state.scaleAnimated.resetAnimation();
this.state.opacityAnimated.resetAnimation();
}
componentWillMount() {
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => false,
onMoveShouldSetPanResponder: (evt, gestureState) => false,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => false,
onPanResponderGrant: ({ nativeEvent }, gestureState) => {
const { locationX, locationY } = nativeEvent;
this.setState(
{ circlePosition: { x: locationX, y: locationY } },
() => this.startScaleAnimation()
);
},
onPanResponderMove: (evt, gestureState) => {
},
onPanResponderTerminationRequest: (evt, gestureState) => false,
onPanResponderRelease: (evt, gestureState) => {
this.props.onPress();
this.startOpacityAnimation();
},
onPanResponderTerminate: (evt, gestureState) => {
this.resetAnimation();
},
onShouldBlockNativeResponder: (evt, gestureState) => {
return true;
},
});
}
render() {
const { circlePosition, rippleRadius, scaleAnimated, opacityAnimated } = this.state;
return (
<View
style={[this.props.style, { overflow: 'hidden' }]}
onLayout={({ nativeEvent: { layout: { width, height } } }) =>
this.setState({ rippleRadius: Math.max(width, height) })}
>
{this.props.children}
<Animated.View style={{
top: scaleAnimated.interpolate({ inputRange: [0, 1], outputRange: [circlePosition.y, circlePosition.y - rippleRadius] }),
left: scaleAnimated.interpolate({ inputRange: [0, 1], outputRange: [circlePosition.x, circlePosition.x - rippleRadius] }),
width: scaleAnimated.interpolate({ inputRange: [0, 1], outputRange: [0, rippleRadius * 2] }),
height: scaleAnimated.interpolate({ inputRange: [0, 1], outputRange: [0, rippleRadius * 2] }),
borderRadius: scaleAnimated.interpolate({ inputRange: [0, 1], outputRange: [0, rippleRadius] }),
backgroundColor: '#00000020',
position: 'absolute',
opacity: opacityAnimated.interpolate({ inputRange: [0, 1], outputRange: [1, 0] }),
}} />
<View style={{ position: 'absolute', height: '100%', width: '100%', backgroundColor: '#00000000' }} {...this.panResponder.panHandlers} />
</View>
);
}
}
export default MaterialButton;
@ryohlan
Copy link
Author

ryohlan commented Mar 20, 2018

Android
9e209d9fa1c1e98f73ad18b46aabdf0f

iOS
82f5641f4ea34f1d7a0821f814b2aa9f

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment