首页 > 解决方案 > 为什么只有一个反应本机并行动画在运行而不是另一个?

问题描述

目标:

创建一个下拉视图,折叠时高度为 75px。展开时,其高度为 225 像素。当按下启动动画的按钮时,它会并行运行两个动画,一个用于高度的计时功能,一个用于不透明度的计时功能。第二部分的目标是使下拉菜单下部的不透明度在 上可用expanded == true

问题:

扩展视图时动画按预期运行,没有问题。但是当折叠下拉菜单时,似乎只有不透明动画运行。问题在于高度,没有正确反映在视图中。它保持与展开时相同的高度。

这是组件:

const ListItem = (props) => {
    const [checkInModal, setCheckInModal] = useState(false);
    const [animatedHeight, setAnimatedHeight] = useState(new Animated.Value(0))
    const [animatedOpacity] = useState(new Animated.Value(0))
    const [expanded, setExpanded] = useState(false);

    const toggleDropdown = () => {
        console.log("BEFORE ANIMATION =>" + "\n" + "expanded: " + expanded + "\n" + "animatedHeight: " + JSON.stringify(animatedHeight) + "\n" + "animatedOpacity: " + JSON.stringify(animatedOpacity))
        if (expanded == true) {
            // collapse dropdown
            Animated.parallel([
                Animated.timing(animatedHeight, {
                    toValue: 0,
                    duration: 200,
                }),
                Animated.timing(animatedOpacity, {
                    toValue: 0,
                    duration: 400,
                })
            ]).start()
            setTimeout(() => console.log("AFTER ANIMATION =>" + "\n" + "animatedHeight: " + JSON.stringify(animatedHeight) + "\n" + "animatedOpacity: " + JSON.stringify(animatedOpacity)), 3000)
            // This alone works properly*
            // Animated.timing(animatedHeight, {
            //  toValue: 0,
            //  duration: 200,
            // }).start()

        } else {
            // expand dropdown
            Animated.sequence([
                Animated.timing(animatedHeight, {
                    toValue: 100,
                    duration: 200,
                }),
                Animated.timing(animatedOpacity, {
                    toValue: 100,
                    duration: 400,
                })
            ]).start()
            setTimeout(() => console.log("AFTER ANIMATION =>" + "\n" + "animatedHeight: " + JSON.stringify(animatedHeight) + "\n" + "animatedOpacity: " + JSON.stringify(animatedOpacity)), 3000)
            // This alone works properly*
            // Animated.timing(animatedHeight, {
            //  toValue: 100,
            //  duration: 200,
            // }).start()
        }
        setExpanded(!expanded)
    }

    const interpolatedHeight = animatedHeight.interpolate({
        inputRange: [0, 100],
        outputRange: [75, 225]
    })

    const interpolatedOpacity = animatedOpacity.interpolate({
        inputRange: [0, 100],
        outputRange: [0.0, 1.0]
    })

    return (
        <Animated.View
            style={[styles.container, { height: interpolatedHeight }]}
        >
            <View style={{ flexDirection: 'row', justifyContent: 'space-between', }}>
                <View style={styles.leftContainer}>
                    <View style={{ flexDirection: 'row', alignItems: 'center' }}>
                        <Text style={styles.title}>{props.title}</Text>
                    </View>
                    <Text style={styles.subtitle}>{time()}</Text>
                </View>

                <View style={styles.rightContainer}>
                    <TouchableOpacity onPress={() => toggleDropdown()} style={styles.toggleBtn}>
                        <Image source={require('../assets/img/chevron-down.png')} resizeMode={'contain'} style={styles.chevron} />
                    </TouchableOpacity>
                </View>
            </View>

            {expanded == true ? (
                <Animated.View style={[styles.bottomContainer, { opacity: interpolatedOpacity }]}>
                    <Components.BodyText text="Subject:" style={{ fontFamily: Fonts.OPENSANS_BOLD }} />

                    <Components.BodyText text={props.subject} />
                </Animated.View>
            ) : null}
        </Animated.View>
    );
};

const styles = StyleSheet.create({
    container: {
        backgroundColor: '#fff',
        borderRadius: 25,
        width: width * 0.95,
        marginBottom: 5,
        marginHorizontal: 5,
        paddingVertical: 15,
        paddingHorizontal: 15
    },
    leftContainer: {
        justifyContent: 'space-between',
    },
    rightContainer: {
        flexDirection: 'row',
        alignItems: 'center'
    },
    title: {
        fontFamily: Fonts.OPENSANS_BOLD,
        fontSize: 20,
        color: '#454A66'
    },
    subtitle: {
        color: '#454A66',
        fontSize: 14
    },
    typeIcon: {
        height: 25,
        width: 25
    },
    chevron: {
        height: 15,
        width: 15
    },
    toggleBtn: {
        borderWidth: 1,
        borderColor: Colors.PRIMARY_DARK,
        borderRadius: 7,
        paddingTop: 4,
        paddingBottom: 2.5,
        paddingHorizontal: 4,
        marginLeft: 10
    },
    bottomContainer: {
        marginVertical: 20
    },
    buttonContainer: {
        flexDirection: 'row',
        width: 250,
        justifyContent: 'space-between',
        alignSelf: 'center',
        marginVertical: 20
    },
    noShadow: {
        elevation: 0,
        shadowOffset: {
            width: 0,
            height: 0
        },
        shadowRadius: 0,
    }
});

export default ListItem;

控制台日志:

折叠时,按下按钮展开会在控制台中记录:

 LOG  BEFORE ANIMATION =>
expanded: false
animatedHeight: 0
animatedOpacity: 0
 LOG  AFTER ANIMATION =>
animatedHeight: 100
animatedOpacity: 100

展开时,按下按钮折叠会在控制台中记录:

 LOG  BEFORE ANIMATION =>
expanded: true
animatedHeight: 100
animatedOpacity: 100
 LOG  AFTER ANIMATION =>
animatedHeight: 100
animatedOpacity: 100

不完全确定那是怎么回事,就像说的那样,不透明动画适用于折叠和展开。如果高度动画根本没有被触及,除了将其从 中取出Animated.parallel(),那么高度动画在展开和折叠时会按预期工作。

关于这里发生了什么的任何想法?

标签: reactjsreact-nativereact-animated

解决方案


当您尝试对不再渲染的内容进行动画处理时,就会出现问题。Animated.parallel显然,如果其中一个元素不再存在,则整个块将不起作用。如果一个动画停止,里面的所有动画Animated.parallel都会停止。stopTogether您可以使用标志覆盖此行为:

    const toggleDropdown = () => {
        if (expanded === true) {
            // collapse dropdown
            Animated.parallel([
                Animated.timing(animatedHeight, {
                    toValue: 0,
                    duration: 200,
                }),
                Animated.timing(animatedOpacity, {
                    toValue: 0,
                    duration: 400,
                })
            ], { stopTogether: false }).start()
            // ...

当您尝试折叠下拉菜单时,该expanded标志设置为false,使“主题”组件不再被渲染,从而使整个动画失败。展开下拉列表时,expanded标志设置为true并且组件现在被渲染,动画工作,但没关系,因为它以前没有回到原始值。

尝试删除条件渲染expanded == true ?部分(我认为它不会影响实际输出)。我已经尝试了没有它的代码,并且似乎可以正常工作。

另一种选择是setExpanded(true)在展开下拉列表之前使元素可见并准备好进行动画处理(并在折叠动画结束时将其设置为 false):

    const toggleDropdown = () => {
        if (expanded === true) {
            // collapse dropdown
            Animated.parallel([
                Animated.timing(animatedHeight, {
                    toValue: 0,
                    duration: 200,
                }),
                Animated.timing(animatedOpacity, {
                    toValue: 0,
                    duration: 400,
                })
            ]).start(() => setExpanded(false))
        }
        else {
            setExpanded(true)
            // expand dropdown
            Animated.sequence([
                Animated.timing(animatedHeight, {
                    toValue: 100,
                    duration: 200,
                }),
                Animated.timing(animatedOpacity, {
                    toValue: 100,
                    duration: 400,
                })
            ]).start()
        }
    }

您将面临一些视觉问题(“主题”部分显示在组件外部,也许您应该添加overflow: hidden或更改时间)。但是应该修复动画问题。


推荐阅读