如何动态设置高度为动画值 post-animation 的组件的高度?
How to dynamically set height of a component whose height is an animated value post-animation?
目标:有一个下拉视图,随着时间的推移动画高度扩展。需要注意的是:展开视图后,它需要能够动态处理是否存在其他视图数据。如果存在,将呈现几个额外的文本组件。
问题:按照目前的情况,动画中父级的高度为固定高度。所以当 additionalContent
被渲染时,它超出了高度固定的父级的边界。我不想 而不是 显式设置父级的高度,因为这样我就无法按照我想要的方式为该方面设置动画。我想按原样保持高度动画,并在 additionalContent
存在时动态调整父级的大小以包含子级
const ListItem = (props) => {
const [checkInModal, setCheckInModal] = useState(false);
const [animatedHeight, setAnimatedHeight] = useState(new Animated.Value(0))
const [animatedOpacity] = useState(new Animated.Value(0))
const [dynamicHeight, setDynamicHeight] = useState(0);
const [expanded, setExpanded] = useState(false);
const toggleDropdown = () => {
if (expanded == true) {
// collapse dropdown
Animated.timing(animatedHeight, {
toValue: 0,
duration: 200,
}).start()
} else {
// expand dropdown
Animated.timing(animatedHeight, {
toValue: 100,
duration: 200,
}).start()
}
setExpanded(!expanded)
}
const renderAdditionalContent = () => {
setDynamicHeight(75);
if (someVariable == true) {
return (
<View> <Text> Some Content </Text> </View>
)
}
}
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 + dynamicHeight }]}
>
<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} />
{ renderAdditionalContent() }
</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;
如何做到这一点?到目前为止,我已经尝试创建一个状态变量 dynamicHeight
并将其设置在呈现附加内容的函数中,但这并没有奏效。
这是零食:https://snack.expo.io/P6WKioG76
澄清编辑:renderAdditionalContent 函数呈现附加内容(显然),此内容可以是从一行字符到多行字符的任何位置。无论字符数如何,组件的主要父容器都需要在其范围内包含所有子容器。就目前而言,如果 additional-content-rendered 的内容过多,内容将溢出组件主父容器的边界,必须避免这种情况。显然,这可以通过简单地不给主要组件容器一个高度来完成。但想法是拥有动画高度并正确包装子内容
有什么建议吗?
已编辑:简单易行的方法
你也可以应用高度100%
,这样我们就不需要计算内部内容的高度,它会根据提供的内容自动调整
const interpolatedHeight = animatedHeight.interpolate({
inputRange: [0, 100],
outputRange: ["0%", "100%"] // <---- HERE
})
为了做到这一点,我在 <Animated.View>
周围添加了 <View>
标签,以获得正确的高度 100%
并且 css 中的一些变化,这看起来更优雅并提供完美解决您的问题。
因此,您可以使用 onLayout
布局计算完成后立即触发此事件
第一步:
// setHeight
const setHeight = (height) => {
setDynamicHeight(prev => prev + height);
}
<View onLayout={(event) => {
var { x, y, width, height } = event.nativeEvent.layout;
setHeight(height); // <--- get the height and add it to total height
}}>
<Text>Subject</Text>
<Text>Subject Content</Text>
{renderAdditionalContent()}
</View>
第二步:
useEffect(() => {
// trigger if only expanded
if (expanded) {
// trigger height animation , whenever there is change in height
Animated.timing(animatedHeight, {
toValue: dynamicHeight, // <--- animate to the given height
duration: 200,
}).start();
}
}, [dynamicHeight]); // <--- check of height change
WORKING DEMO(可以通过添加remove text来测试)
您可以使用 React refs。这允许您直接访问组件。
const el = React.useRef(null);
将 el 分配给您拥有的任何内容的容器 div 的引用。然后 hide/show div 使用可见性样式:
<div style={{ visibility: !expanded && "hidden" }} ref={el}>
<View /* snip */>
{ renderAdditionalContent() }
/* -- snip -- */
</View>
</div>
然后在 toggleDropdown 函数中,您可以通过 .current.offsetHeight 属性:
访问组件的高度
Animated.timing(animatedHeight, {
toValue: el.current.offsetHeight,
duration: 200,
}).start()
Refs 可用于任何 html 元素,并允许您访问该元素的原始属性。动画可能是它们最常见的用例之一。
演示:https://snack.expo.io/heLsrZNpz
您可以在这里阅读更多内容:https://reactjs.org/docs/refs-and-the-dom.html
这是您的代码的工作小吃,具有动态下拉高度:https://snack.expo.io/4mT5Xj6qF
您可以通过更改 thisIsAValue
常量来更改高度。
老实说,您的代码非常接近,只是缺少一些点点滴滴:
- 在设置高度动画时不需要使用
interpolate
,您可以让 <Animated.View>
直接计算高度。如果你想做动画就需要插值,比如旋转,你需要计算元素必须移动的度数。
- 您需要将动态高度传递到动画中,作为您的
to:
值
- 我设置了一个简单的
useEffect
钩子来检查 someVariable
和 dynamicHeight
prop 中的变化
此解决方案可让您通过道具动态设置高度。
但是,如果您想根据 View
中存在的元素计算高度,您可能需要查看@vivek-doshi 的答案
目标:有一个下拉视图,随着时间的推移动画高度扩展。需要注意的是:展开视图后,它需要能够动态处理是否存在其他视图数据。如果存在,将呈现几个额外的文本组件。
问题:按照目前的情况,动画中父级的高度为固定高度。所以当 additionalContent
被渲染时,它超出了高度固定的父级的边界。我不想 而不是 显式设置父级的高度,因为这样我就无法按照我想要的方式为该方面设置动画。我想按原样保持高度动画,并在 additionalContent
存在时动态调整父级的大小以包含子级
const ListItem = (props) => {
const [checkInModal, setCheckInModal] = useState(false);
const [animatedHeight, setAnimatedHeight] = useState(new Animated.Value(0))
const [animatedOpacity] = useState(new Animated.Value(0))
const [dynamicHeight, setDynamicHeight] = useState(0);
const [expanded, setExpanded] = useState(false);
const toggleDropdown = () => {
if (expanded == true) {
// collapse dropdown
Animated.timing(animatedHeight, {
toValue: 0,
duration: 200,
}).start()
} else {
// expand dropdown
Animated.timing(animatedHeight, {
toValue: 100,
duration: 200,
}).start()
}
setExpanded(!expanded)
}
const renderAdditionalContent = () => {
setDynamicHeight(75);
if (someVariable == true) {
return (
<View> <Text> Some Content </Text> </View>
)
}
}
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 + dynamicHeight }]}
>
<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} />
{ renderAdditionalContent() }
</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;
如何做到这一点?到目前为止,我已经尝试创建一个状态变量 dynamicHeight
并将其设置在呈现附加内容的函数中,但这并没有奏效。
这是零食:https://snack.expo.io/P6WKioG76
澄清编辑:renderAdditionalContent 函数呈现附加内容(显然),此内容可以是从一行字符到多行字符的任何位置。无论字符数如何,组件的主要父容器都需要在其范围内包含所有子容器。就目前而言,如果 additional-content-rendered 的内容过多,内容将溢出组件主父容器的边界,必须避免这种情况。显然,这可以通过简单地不给主要组件容器一个高度来完成。但想法是拥有动画高度并正确包装子内容
有什么建议吗?
已编辑:简单易行的方法
你也可以应用高度100%
,这样我们就不需要计算内部内容的高度,它会根据提供的内容自动调整
const interpolatedHeight = animatedHeight.interpolate({
inputRange: [0, 100],
outputRange: ["0%", "100%"] // <---- HERE
})
为了做到这一点,我在 <Animated.View>
周围添加了 <View>
标签,以获得正确的高度 100%
并且 css 中的一些变化,这看起来更优雅并提供完美解决您的问题。
因此,您可以使用 onLayout
布局计算完成后立即触发此事件
第一步:
// setHeight
const setHeight = (height) => {
setDynamicHeight(prev => prev + height);
}
<View onLayout={(event) => {
var { x, y, width, height } = event.nativeEvent.layout;
setHeight(height); // <--- get the height and add it to total height
}}>
<Text>Subject</Text>
<Text>Subject Content</Text>
{renderAdditionalContent()}
</View>
第二步:
useEffect(() => {
// trigger if only expanded
if (expanded) {
// trigger height animation , whenever there is change in height
Animated.timing(animatedHeight, {
toValue: dynamicHeight, // <--- animate to the given height
duration: 200,
}).start();
}
}, [dynamicHeight]); // <--- check of height change
WORKING DEMO(可以通过添加remove text来测试)
您可以使用 React refs。这允许您直接访问组件。
const el = React.useRef(null);
将 el 分配给您拥有的任何内容的容器 div 的引用。然后 hide/show div 使用可见性样式:
<div style={{ visibility: !expanded && "hidden" }} ref={el}>
<View /* snip */>
{ renderAdditionalContent() }
/* -- snip -- */
</View>
</div>
然后在 toggleDropdown 函数中,您可以通过 .current.offsetHeight 属性:
访问组件的高度Animated.timing(animatedHeight, {
toValue: el.current.offsetHeight,
duration: 200,
}).start()
Refs 可用于任何 html 元素,并允许您访问该元素的原始属性。动画可能是它们最常见的用例之一。
演示:https://snack.expo.io/heLsrZNpz
您可以在这里阅读更多内容:https://reactjs.org/docs/refs-and-the-dom.html
这是您的代码的工作小吃,具有动态下拉高度:https://snack.expo.io/4mT5Xj6qF
您可以通过更改 thisIsAValue
常量来更改高度。
老实说,您的代码非常接近,只是缺少一些点点滴滴:
- 在设置高度动画时不需要使用
interpolate
,您可以让<Animated.View>
直接计算高度。如果你想做动画就需要插值,比如旋转,你需要计算元素必须移动的度数。 - 您需要将动态高度传递到动画中,作为您的
to:
值 - 我设置了一个简单的
useEffect
钩子来检查someVariable
和dynamicHeight
prop 中的变化
此解决方案可让您通过道具动态设置高度。
但是,如果您想根据 View
中存在的元素计算高度,您可能需要查看@vivek-doshi 的答案