You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native如何实现随内容自适应高度的View动画?

问题描述

我有一个嵌套FlatList的View,原本通过设置maxHeight实现了内容自适应高度,但无动画效果。正常无动画状态效果如下:
正常状态1
正常状态2

我尝试用Animated实现高度动画,代码如下,但出现了异常效果:
异常状态

const [searchBoxHeight, setSearchBoxHeight] = useState(0);
const animatedSearchBoxHeight = useRef(new Animated.Value(0)).current;

 useEffect(() => {
    Animated.timing(animatedSearchBoxHeight, {
      toValue: searchBoxHeight,
      duration: 300,
      useNativeDriver: false,
    }).start();
  }, [searchBoxHeight]);

<Animated.View style={{ height: animatedSearchBoxHeight }}>
    <View
      style={styles.searchBox}
      onLayout={(event) => {
        setSearchBoxHeight(event.nativeEvent.layout.height);
      }}
    >
      <FlatList
        nestedScrollEnabled
        showsVerticalScrollIndicator
        data={getCetaceansFiltered()}
        keyExtractor={(item) => item.id}
        renderItem={renderItem}
        ItemSeparatorComponent={() => (
          <ListItemSeparator
            width="95%"
            color={defaultStyles.colors.transparent}
          />
        )}
      />
    </View>
 </Animated.View>

// 样式定义
searchBox: {
    paddingVertical: 12,
    width: "100%",
    backgroundColor: defaultStyles.colors.transparent,
    borderRadius: 20,
    marginTop: 5,
    maxHeight: 250,
  },
问题分析

异常的核心原因是动画容器的固定高度与内部带maxHeight的View产生布局冲突:

  1. 内部View的onLayout会返回受maxHeight限制后的高度,当内容超过250时,动画容器被固定为250高度,但内部View的滚动区域可能被挤压;
  2. onLayout会在组件渲染、布局变化时多次触发,导致动画被频繁启动,出现跳变或异常拉伸。
解决方案

方案1:直接给内部View的maxHeight添加动画(推荐)

去掉额外的Animated.View嵌套,直接将maxHeight设为动画值,既保留自适应特性,又实现平滑动画:

import { Animated, useState, useRef, useEffect } from 'react-native';

// 初始化动画值,默认收起为0,可根据需求调整初始值
const animatedMaxHeight = useRef(new Animated.Value(0)).current;
const [contentHeight, setContentHeight] = useState(0);

// 监听FlatList内容高度变化,取内容高度与250的最小值作为动画目标
const handleContentSizeChange = (_, height) => {
  setContentHeight(Math.min(height, 250));
};

// 触发动画
const startHeightAnimation = () => {
  Animated.timing(animatedMaxHeight, {
    toValue: contentHeight,
    duration: 300,
    useNativeDriver: false,
  }).start();
};

// 搜索结果变化时自动触发动画
useEffect(() => {
  startHeightAnimation();
}, [getCetaceansFiltered()]);

return (
  <View
    style={[
      styles.searchBox,
      { maxHeight: animatedMaxHeight } // 用动画值替代固定maxHeight
    ]}
  >
    <FlatList
      nestedScrollEnabled
      showsVerticalScrollIndicator
      data={getCetaceansFiltered()}
      keyExtractor={(item) => item.id}
      renderItem={renderItem}
      ItemSeparatorComponent={() => (
        <ListItemSeparator
          width="95%"
          color={defaultStyles.colors.transparent}
        />
      )}
      onContentSizeChange={handleContentSizeChange}
    />
  </View>
);

// 样式移除固定maxHeight
searchBox: {
    paddingVertical: 12,
    width: "100%",
    backgroundColor: defaultStyles.colors.transparent,
    borderRadius: 20,
    marginTop: 5,
  },

方案2:修复原结构的布局冲突

如果坚持使用原嵌套结构,需要确保动画容器的高度计算逻辑正确,避免频繁触发动画:

import { Animated, useState, useRef, useEffect } from 'react-native';

const [searchBoxHeight, setSearchBoxHeight] = useState(0);
const animatedSearchBoxHeight = useRef(new Animated.Value(0)).current;

// 仅在搜索结果变化时更新动画,避免onLayout频繁触发
useEffect(() => {
  // 延迟获取高度,确保FlatList渲染完成
  setTimeout(() => {
    const targetHeight = Math.min(searchBoxHeight, 250);
    Animated.timing(animatedSearchBoxHeight, {
      toValue: targetHeight,
      duration: 300,
      useNativeDriver: false,
    }).start();
  }, 50);
}, [getCetaceansFiltered()]);

return (
  <Animated.View 
    style={{ 
      height: animatedSearchBoxHeight,
      overflow: 'hidden' // 确保超出内容被正确裁剪
    }}
  >
    <View
      style={styles.searchBox}
      onLayout={(event) => {
        // 获取内部View的自然高度(不受外部容器限制)
        setSearchBoxHeight(event.nativeEvent.layout.height);
      }}
    >
      <FlatList
        nestedScrollEnabled
        showsVerticalScrollIndicator
        data={getCetaceansFiltered()}
        keyExtractor={(item) => item.id}
        renderItem={renderItem}
        ItemSeparatorComponent={() => (
          <ListItemSeparator
            width="95%"
            color={defaultStyles.colors.transparent}
          />
        )}
      />
    </View>
  </Animated.View>
);

关键注意事项

  • 动画布局属性(height/maxHeight)必须关闭useNativeDriver,原生驱动不支持此类布局动画;
  • 避免依赖onLayout频繁触发动画,应绑定业务数据源变化作为动画触发时机;
  • 使用FlatList的onContentSizeChange可以更精准地获取实际内容高度,适配不同数量的列表项。

内容的提问来源于stack exchange,提问作者foytecna

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 02:05:45