@uiw/react-native
Version:
UIW for React Native
149 lines (148 loc) • 4.05 kB
JavaScript
import React, { useState, useRef, useEffect, useMemo } from 'react';
import { StyleSheet, View, Dimensions, TouchableOpacity,
// 点击事件
Platform,
// 动画
UIManager,
// 动画
Animated,
// 动画
PanResponder // 手指拖拽
} from 'react-native';
import Icon from '../Icon';
import Item from './SpeedDialItem';
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
let MainHeight = Dimensions.get('window').height;
function SpeedDial(props) {
const {
icon = ['plus', 'close'],
style,
onOpen,
onClose,
children = new Array(),
transitionDuration = 200,
isDrag = false,
bottom = 350,
right = 40,
...other
} = props;
const [success, setSuccess] = useState(false);
const [iconName, setIconName] = useState('plus');
const pan = useRef(new Animated.ValueXY()).current;
const panResponder = useRef(PanResponder.create({
onMoveShouldSetPanResponder: () => isDrag,
onPanResponderGrant: () => {
pan.setOffset({
x: pan.x._value,
y: pan.y._value
});
},
onPanResponderMove: Animated.event([null, {
dx: pan.x,
dy: pan.y
}], {
useNativeDriver: false
}),
onPanResponderRelease: () => {
pan.flattenOffset();
}
})).current;
const fadeAnim = useRef(new Array(children.length).fill(new Animated.Value(0))).current;
useEffect(() => {
Animated.stagger(100, fadeAnim.map((_, i) => Animated.timing(fadeAnim[i], {
toValue: iconName !== 'plus' ? 1 : 0,
duration: transitionDuration,
useNativeDriver: true
}))[iconName !== 'plus' ? 'sort' : 'reverse']()).start(({
finished
}) => {
if (iconName === 'plus') {
if (!finished) {
fadeAnim.forEach((_, i) => Animated.timing(fadeAnim[i], {
toValue: iconName !== 'plus' ? 1 : 0,
duration: 50,
useNativeDriver: true
}).stop());
}
setSuccess(false);
}
});
}, [iconName]);
const PlusDom = useMemo(() => {
if (icon[0] instanceof Object) {
return <React.Fragment>{icon[0]}</React.Fragment>;
} else {
return <Icon name={icon[0]} color="#fff" size={18} />;
}
}, []);
const CloseDom = useMemo(() => {
if (icon[1] instanceof Object) {
return <React.Fragment>{icon[1]}</React.Fragment>;
} else {
return <Icon name={icon[1]} color="#fff" size={18} />;
}
}, []);
const onOpenHome = () => {
setSuccess(true);
if (iconName === 'plus') {
setIconName('close');
onOpen && onOpen();
} else {
setIconName('plus');
onClose && onClose();
}
};
return <View>
<Animated.View style={[styles.viewPosition, {
bottom: bottom - MainHeight,
right: right
}, {
transform: [{
translateX: pan.x
}, {
translateY: pan.y
}]
}]}
// {...panResponder.panHandlers}
>
{success && children.map((item, i) => <Animated.View style={[styles.fadingContainer, {
// Bind opacity to animated value
opacity: fadeAnim[i]
}]} key={i}>
<Item {...item} move={panResponder.panHandlers} />
</Animated.View>)}
<View {...panResponder.panHandlers} style={{
alignItems: 'flex-end'
}}>
<TouchableOpacity activeOpacity={1} onPress={onOpenHome}>
<View style={[styles.homeContainer, {
...style
}]} {...other}>
{iconName === 'plus' ? PlusDom : CloseDom}
</View>
</TouchableOpacity>
</View>
</Animated.View>
</View>;
}
const styles = StyleSheet.create({
fadingContainer: {
alignItems: 'flex-end',
opacity: 0
},
viewPosition: {
position: 'absolute'
},
homeContainer: {
width: 60,
height: 60,
backgroundColor: '#b779e2',
borderRadius: 30,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center'
}
});
export default SpeedDial;