如何用Reanimated 3.4.1实现View平滑扩宽并带动后续元素动画
解决方案
直接动画flex属性无法实现平滑过渡,因为flex是布局计算的权重参数,并非可直接插值的样式属性。要实现你要的效果,需要通过动态计算目标宽度+动画宽度属性来实现,同时利用Flex布局的自然特性带动后续元素移动。
核心思路
- 先获取父容器的总宽度,减去#1和#3的固定/动态宽度,得到#2的目标填充宽度
- 使用Reanimated的共享值(Shared Value)控制#2的宽度,通过
withTiming实现从0到目标宽度的平滑过渡 - 利用Flex布局的流式排列特性,#2宽度扩展时会自然推动#3,无需额外为#3做动画
完整代码示例
import React, { useLayoutEffect } from 'react'; import { View, TouchableOpacity, Text } from 'react-native'; import Animated, { useSharedValue, useAnimatedStyle, withTiming, useAnimatedRef, measure } from 'react-native-reanimated'; const ExpandableRow = () => { const containerRef = useAnimatedRef(); const box1Ref = useAnimatedRef(); const box3Ref = useAnimatedRef(); const box2Width = useSharedValue(0); let targetWidth = 0; // 计算#2的目标宽度:父容器宽度 - #1宽度 - #3宽度 useLayoutEffect(() => { const calculateTargetWidth = async () => { const containerLayout = await measure(containerRef); const box1Layout = await measure(box1Ref); const box3Layout = await measure(box3Ref); targetWidth = containerLayout.width - box1Layout.width - box3Layout.width; }; calculateTargetWidth(); }, []); // 绑定动画样式 const box2AnimatedStyle = useAnimatedStyle(() => ({ width: box2Width.value, })); // 启动展开动画 const expandBox2 = () => { box2Width.value = withTiming(targetWidth, { duration: 600 }); }; // 启动收起动画 const collapseBox2 = () => { box2Width.value = withTiming(0, { duration: 600 }); }; return ( <View style={{ flex: 1, justifyContent: 'center', paddingHorizontal: 20 }}> {/* 父容器:横向排列 */} <Animated.View ref={containerRef} style={{ flexDirection: 'row', alignItems: 'center', height: 50, backgroundColor: '#f5f5f5', borderRadius: 8 }} > {/* #1:固定宽度元素 */} <Animated.View ref={box1Ref} style={{ width: 80, height: '100%', backgroundColor: '#ff6b6b', justifyContent: 'center', alignItems: 'center', borderTopLeftRadius: 8, borderBottomLeftRadius: 8 }} > <Text style={{ color: '#fff', fontWeight: 'bold' }}>#1</Text> </Animated.View> {/* #2:动画宽度元素 */} <Animated.View style={[ box2AnimatedStyle, { height: '100%', backgroundColor: '#4ecdc4', justifyContent: 'center', alignItems: 'center' } ]} > <Text style={{ color: '#fff', fontWeight: 'bold' }}>#2</Text> </Animated.View> {/* #3:固定宽度元素 */} <Animated.View ref={box3Ref} style={{ width: 80, height: '100%', backgroundColor: '#45b7d1', justifyContent: 'center', alignItems: 'center', borderTopRightRadius: 8, borderBottomRightRadius: 8 }} > <Text style={{ color: '#fff', fontWeight: 'bold' }}>#3</Text> </Animated.View> </Animated.View> {/* 控制按钮 */} <View style={{ flexDirection: 'row', gap: 12, marginTop: 24 }}> <TouchableOpacity onPress={expandBox2} style={{ flex: 1, padding: 12, backgroundColor: '#2ecc71', borderRadius: 8 }} > <Text style={{ color: '#fff', textAlign: 'center', fontWeight: 'bold' }}>展开#2</Text> </TouchableOpacity> <TouchableOpacity onPress={collapseBox2} style={{ flex: 1, padding: 12, backgroundColor: '#e74c3c', borderRadius: 8 }} > <Text style={{ color: '#fff', textAlign: 'center', fontWeight: 'bold' }}>收起#2</Text> </TouchableOpacity> </View> </View> ); }; export default ExpandableRow;
关键细节说明
- 尺寸测量:使用
useAnimatedRef和measureAPI获取元素尺寸,确保在Reanimated的上下文里异步获取准确的布局信息 - 动画控制:通过
useAnimatedStyle将共享值绑定到宽度样式,withTiming提供平滑的过渡曲线 - 后续元素动画:由于父容器是
row方向的Flex布局,#2宽度扩展时会自动挤压#3的位置,#3会自然获得平滑的移动动画,无需额外处理 - 动态适配:如果#1或#3的宽度是动态变化的(比如文本长度变化),可以用
useAnimatedReaction监听它们的尺寸变化,实时更新targetWidth并调整动画值
内容的提问来源于stack exchange,提问作者Can Poyrazoğlu
相关产品推荐
相关产品推荐

