Skip to content

Instantly share code, notes, and snippets.

@ryohlan ryohlan/MaterialButton.js
Last active Mar 20, 2018

Embed
What would you like to do?
マテリアルデザインの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

This comment has been minimized.

Copy link
Owner Author

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
You can’t perform that action at this time.