@uiw/react-native
Version:
UIW for React Native
135 lines (133 loc) • 4.79 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';
import { useTheme } from '@shopify/restyle';
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
let MainHeight = Dimensions.get('window').height;
function SpeedDial(props) {
const theme = useTheme();
const styles = createStyles({
bgColor: theme.colors.primary_background || '#3578e5',
});
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={theme.colors.white || '#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={theme.colors.white || '#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 }] },
]}>
{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>);
}
function createStyles({ bgColor }) {
return StyleSheet.create({
fadingContainer: {
alignItems: 'flex-end',
opacity: 0,
},
viewPosition: {
position: 'absolute',
},
homeContainer: {
width: 60,
height: 60,
backgroundColor: bgColor,
borderRadius: 30,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
});
}
export default SpeedDial;