UNPKG

@uiw/react-native

Version:
135 lines (133 loc) 4.79 kB
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;