UNPKG

@uiw/react-native

Version:
207 lines (206 loc) 6.18 kB
import React, { useState, useEffect } from 'react'; import invoke from 'lodash/invoke'; import { StyleSheet, Animated, Easing, LayoutAnimation, View, TouchableOpacity, Platform, Dimensions } from 'react-native'; import { colors } from '../utils'; const PEEP = 8; const DURATION = 300; const MARGIN_BOTTOM = 24; const buttonStartValue = 0.8; export default function CardCollapse(props) { const { isCollapsed, children, contentContainerStyle, itemBorderRadius, disablePresses, containerStyle, onCollapseChanged } = props; const getAnimatedScales = () => { return React.Children.map(children, (_item, index) => { return new Animated.Value(getItemScale(index)); }); }; const [collapsed, setCollapsed] = useState(isCollapsed); const [firstItemHeight, setFirstItemHeight] = useState(undefined); const animatedScale = new Animated.Value(collapsed ? buttonStartValue : 1); const animatedOpacity = new Animated.Value(collapsed ? buttonStartValue : 1); const animatedContentOpacity = new Animated.Value(collapsed ? 0 : 1); const animatedScaleArray = getAnimatedScales(); const itemsCount = React.Children.count(children); const easeOut = Easing.bezier(0, 0, 0.58, 1); const Container = !disablePresses ? TouchableOpacity : View; useEffect(() => { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); }, [collapsed]); // 动画 const animate = async () => { return Promise.all([animateValues(), animateCards()]); }; const animateValues = () => { const newValue = collapsed ? buttonStartValue : 1; return new Promise(resolve => { Animated.parallel([Animated.timing(animatedOpacity, { duration: DURATION, toValue: Number(newValue), useNativeDriver: true }), Animated.timing(animatedScale, { toValue: Number(newValue), easing: easeOut, duration: DURATION, useNativeDriver: true }), Animated.timing(animatedContentOpacity, { toValue: Number(collapsed ? 0 : 1), easing: easeOut, duration: DURATION, useNativeDriver: true })]).start(resolve); }); }; function getItemScale(index) { if (collapsed) { return 0.9; } return 1; } const animateCards = () => { const promises = []; for (let index = 0; index < itemsCount; index++) { const newScale = getItemScale(index); promises.push(new Promise(resolve => { Animated.timing(animatedScaleArray[index], { toValue: Number(newScale), easing: easeOut, duration: DURATION, useNativeDriver: true }).start(resolve); })); } return Promise.all(promises); }; // 关闭折叠 const close = async () => { setCollapsed(true); invoke(props, 'onCollapseWillChange', true); if (onCollapseChanged) { await animate(); onCollapseChanged(true); } else { animate(); } }; // 打开折叠 const open = async () => { setCollapsed(false); invoke(props, 'onCollapseWillChange', false); if (onCollapseChanged) { await animate(); onCollapseChanged(false); } else { animate(); } }; const getTop = index => { let start = 0; if (index === itemsCount - 2) { start += PEEP; } if (index === itemsCount - 1) { start += PEEP * 2; } return start; }; const getStyle = index => { const top = getTop(index); if (collapsed) { return { position: index !== 0 ? 'absolute' : undefined, top }; } return { marginBottom: MARGIN_BOTTOM, marginTop: index === 0 ? 40 : undefined }; }; const onLayout = event => { const height = event.nativeEvent.layout.height; if (height) { setFirstItemHeight(height); } }; const onItemPress = index => { invoke(props, 'onItemPress', index); }; const renderItem = (item, index) => { return <Animated.View key={index} onLayout={index === 0 ? onLayout : undefined} style={[Platform.OS === 'ios' && styles.containerShadow, getStyle(index), { borderRadius: Platform.OS === 'ios' ? itemBorderRadius : undefined, alignSelf: 'center', zIndex: itemsCount - index, transform: [{ scaleX: animatedScaleArray[index] }], width: Dimensions.get('screen').width - 40, height: collapsed ? firstItemHeight : undefined }]} collapsable={false}> <Container style={[contentContainerStyle, styles.card, { borderRadius: itemBorderRadius }]} onPress={() => disablePresses && onItemPress(index)}> <TouchableOpacity onPress={close}> <Animated.View style={index !== 0 ? { opacity: animatedContentOpacity } : undefined} collapsable={false}> {item} </Animated.View> </TouchableOpacity> </Container> </Animated.View>; }; return <View style={containerStyle}> <View style={{ marginBottom: PEEP * 3 }}> {/* <Animated.View style={{ position: 'absolute', right: 0, opacity: animatedOpacity, transform: [{ scale: animatedScale }], }} > {collapsed ? null : ( <TouchableOpacity onPress={close}> <Icon xml={down} size={30} /> </TouchableOpacity> )} </Animated.View> */} {React.Children.map(children, (item, index) => { return renderItem(item, index); })} {collapsed && <TouchableOpacity onPress={open} activeOpacity={1} style={[styles.touchable, { height: firstItemHeight ? firstItemHeight + PEEP * 2 : undefined, zIndex: itemsCount }]} />} </View> </View>; } const styles = StyleSheet.create({ touchable: { position: 'absolute', width: '100%' }, containerShadow: { backgroundColor: colors.white, shadowColor: colors.colorsPalette.dark40, shadowOpacity: 0.25, shadowRadius: 12, shadowOffset: { height: 5, width: 0 } }, card: { overflow: 'hidden', flexShrink: 1 } });