为什么只有一个反应本机并行动画 运行 而另一个没有?
Why is only one react native parallel animation running and not the other?
目标:
创建一个下拉视图,折叠时高度为 75 像素。展开后,它的高度为 225px。当按下启动动画的按钮时,它会并行运行两个动画,一个是高度计时函数,一个是不透明度计时函数。目标的第二部分是动画下拉列表下部的不透明度在 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()
中取出外,高度动画根本没有被触及,那么高度动画在展开和折叠时会按预期工作。
对这里发生的事情有什么想法吗?
当您尝试为不再渲染的东西制作动画时,问题就来了。显然,如果其中一个元素不再存在,整个 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
,使得 "subject" 组件不再被渲染,从而使整个动画失败。展开下拉列表时,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()
}
}
您将面临一些视觉问题("Subject" 部分显示在组件外部,也许您应该添加一个 overflow: hidden
或更改时间)。但是动画问题应该已经解决了。
给你
动画有 2 个问题:
- 在折叠动画时,制作
opacity 0
受尊重的元素不见了,所以你可以 setTimeout
而折叠 是
发生了,一旦动画完成你就可以删除元素,
我们只需要删除它,因为在扩展时我们需要
尽快添加元素,因此在扩展 时不需要setTimeout
Animated.parallel
默认情况下,如果其中一个动画停止,它们将全部停止。您可以使用
stopTogether 标志。
// take the animation timeframe in array
const animationDurations = [500, 1000];
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 = () => {
if (expanded == true) {
Animated.parallel([
Animated.timing(animatedHeight, {
toValue: 0,
duration: animationDurations[0],
}),
Animated.timing(animatedOpacity, {
toValue: 0,
duration: animationDurations[1],
})
], {
stopTogether: false // <--- so that all animation get completed
}).start()
// get max animation time and then toggle the element out
setTimeout(() => {
setExpanded(!expanded)
}, Math.max(...animationDurations));
} else {
// expand dropdown
Animated.sequence([
Animated.timing(animatedHeight, {
toValue: 100,
duration: animationDurations[0],
}),
Animated.timing(animatedOpacity, {
toValue: 100,
duration: animationDurations[1],
})
]).start()
setExpanded(!expanded) // <---- Toggle asap to for showing purpose
}
}
const interpolatedHeight = animatedHeight.interpolate({
inputRange: [0, 100],
outputRange: ["0%", "100%"]
})
虽然我标记正确并奖励了另一个答案,但我发布了我实现的代码,它产生了所需的功能。正如@Vivek Doshi 在我的另一篇帖子中指出的那样,useEffect()
对我的努力有很大帮助。
我更改的两个主要内容是在我的切换功能中切换一个简单的布尔值:
const toggleTest = () => {
setExpanded(!expanded)
}
并实施 useEffect():
const ListItem = (props) => {
const [checkInModal, setCheckInModal] = useState(false);
const [animatedHeight] = useState(new Animated.Value(75))
const [animatedOpacity] = useState(new Animated.Value(0))
const [dynamicHeight, setDynamicHeight] = useState(0);
const [expanded, setExpanded] = useState(false);
useEffect(() => {
if (expanded) {
// expand dropdown
Animated.parallel([
Animated.timing(animatedHeight, {
toValue: 225 + dynamicHeight,
duration: 200,
}),
Animated.timing(animatedOpacity, {
toValue: 100,
duration: 200,
})
]).start()
} else {
Animated.parallel([
Animated.timing(animatedHeight, {
toValue: 75,
duration: 200,
}),
Animated.timing(animatedOpacity, {
toValue: 0,
duration: 400,
})
]).start()
}
}, [dynamicHeight, expanded])
确保将适当的依赖项添加到 useEffect 参数!
目标:
创建一个下拉视图,折叠时高度为 75 像素。展开后,它的高度为 225px。当按下启动动画的按钮时,它会并行运行两个动画,一个是高度计时函数,一个是不透明度计时函数。目标的第二部分是动画下拉列表下部的不透明度在 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()
中取出外,高度动画根本没有被触及,那么高度动画在展开和折叠时会按预期工作。
对这里发生的事情有什么想法吗?
当您尝试为不再渲染的东西制作动画时,问题就来了。显然,如果其中一个元素不再存在,整个 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
,使得 "subject" 组件不再被渲染,从而使整个动画失败。展开下拉列表时,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()
}
}
您将面临一些视觉问题("Subject" 部分显示在组件外部,也许您应该添加一个 overflow: hidden
或更改时间)。但是动画问题应该已经解决了。
给你
动画有 2 个问题:
- 在折叠动画时,制作
opacity 0
受尊重的元素不见了,所以你可以setTimeout
而折叠 是 发生了,一旦动画完成你就可以删除元素, 我们只需要删除它,因为在扩展时我们需要 尽快添加元素,因此在扩展 时不需要 Animated.parallel
默认情况下,如果其中一个动画停止,它们将全部停止。您可以使用 stopTogether 标志。
setTimeout
// take the animation timeframe in array
const animationDurations = [500, 1000];
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 = () => {
if (expanded == true) {
Animated.parallel([
Animated.timing(animatedHeight, {
toValue: 0,
duration: animationDurations[0],
}),
Animated.timing(animatedOpacity, {
toValue: 0,
duration: animationDurations[1],
})
], {
stopTogether: false // <--- so that all animation get completed
}).start()
// get max animation time and then toggle the element out
setTimeout(() => {
setExpanded(!expanded)
}, Math.max(...animationDurations));
} else {
// expand dropdown
Animated.sequence([
Animated.timing(animatedHeight, {
toValue: 100,
duration: animationDurations[0],
}),
Animated.timing(animatedOpacity, {
toValue: 100,
duration: animationDurations[1],
})
]).start()
setExpanded(!expanded) // <---- Toggle asap to for showing purpose
}
}
const interpolatedHeight = animatedHeight.interpolate({
inputRange: [0, 100],
outputRange: ["0%", "100%"]
})
虽然我标记正确并奖励了另一个答案,但我发布了我实现的代码,它产生了所需的功能。正如@Vivek Doshi 在我的另一篇帖子中指出的那样,useEffect()
对我的努力有很大帮助。
我更改的两个主要内容是在我的切换功能中切换一个简单的布尔值:
const toggleTest = () => {
setExpanded(!expanded)
}
并实施 useEffect():
const ListItem = (props) => {
const [checkInModal, setCheckInModal] = useState(false);
const [animatedHeight] = useState(new Animated.Value(75))
const [animatedOpacity] = useState(new Animated.Value(0))
const [dynamicHeight, setDynamicHeight] = useState(0);
const [expanded, setExpanded] = useState(false);
useEffect(() => {
if (expanded) {
// expand dropdown
Animated.parallel([
Animated.timing(animatedHeight, {
toValue: 225 + dynamicHeight,
duration: 200,
}),
Animated.timing(animatedOpacity, {
toValue: 100,
duration: 200,
})
]).start()
} else {
Animated.parallel([
Animated.timing(animatedHeight, {
toValue: 75,
duration: 200,
}),
Animated.timing(animatedOpacity, {
toValue: 0,
duration: 400,
})
]).start()
}
}, [dynamicHeight, expanded])
确保将适当的依赖项添加到 useEffect 参数!