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

Flutter中如何实现带动画的自适应内在尺寸Widget切换?

解决AnimatedSize从大到小无动画的问题

问题核心是:AnimatedSize在缩小阶段无法获取旧子Widget的尺寸信息(因为AnimatedSwitcher已经把旧组件移除了),导致没有过渡动画。下面是直接可用的解决方案:

核心思路

预缓存所有子Widget的内在尺寸,用AnimatedContainer主动控制宽高的过渡动画,同时结合AnimatedSwitcher做内容切换,不管放大还是缩小都能得到平滑的动画效果。

完整代码实现

class AnimatedTabContent extends StatefulWidget {
  final List<Widget> children;
  final int currentIndex;
  final Duration duration;

  const AnimatedTabContent({
    super.key,
    required this.children,
    required this.currentIndex,
    this.duration = const Duration(milliseconds: 300),
  });

  @override
  State<AnimatedTabContent> createState() => _AnimatedTabContentState();
}

class _AnimatedTabContentState extends State<AnimatedTabContent> {
  List<Size?> _childSizes = [];
  Size? _currentSize;

  @override
  void initState() {
    super.initState();
    _childSizes = List.generate(widget.children.length, (_) => null);
    WidgetsBinding.instance.addPostFrameCallback((_) => _cacheSizes());
  }

  @override
  void didUpdateWidget(covariant AnimatedTabContent oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.children.length != widget.children.length) {
      _childSizes = List.generate(widget.children.length, (_) => null);
      WidgetsBinding.instance.addPostFrameCallback((_) => _cacheSizes());
    }
    _currentSize = _childSizes[widget.currentIndex];
  }

  Future<void> _cacheSizes() async {
    final newSizes = <Size?>[];
    for (final child in widget.children) {
      final key = GlobalKey();
      // 用临时Offstage组件测量子Widget的内在尺寸
      final tempWidget = Offstage(key: key, child: child);
      await _buildAndMeasure(tempWidget, key);
      newSizes.add(key.currentContext?.size);
    }
    setState(() {
      _childSizes = newSizes;
      _currentSize = _childSizes[widget.currentIndex];
    });
  }

  Future<void> _buildAndMeasure(Widget widget, GlobalKey key) {
    final completer = Completer<void>();
    final overlay = Overlay.of(context);
    final entry = OverlayEntry(builder: (context) => widget);
    overlay.insert(entry);
    WidgetsBinding.instance.addPostFrameCallback((_) {
      completer.complete();
      entry.remove();
    });
    return completer.future;
  }

  @override
  Widget build(BuildContext context) {
    // 尺寸未缓存完成时直接显示当前组件
    if (_currentSize == null) {
      return widget.children[widget.currentIndex];
    }
    return AnimatedContainer(
      duration: widget.duration,
      curve: Curves.easeInOut,
      width: _currentSize!.width,
      height: _currentSize!.height,
      child: AnimatedSwitcher(
        duration: widget.duration,
        child: widget.children[widget.currentIndex],
      ),
    );
  }
}

关键细节说明

  1. 尺寸预缓存:通过Overlay临时渲染每个子Widget(用Offstage隐藏),获取它们的内在尺寸并缓存,这样切换时能直接拿到新旧尺寸数据。
  2. AnimatedContainer控尺寸:不再依赖AnimatedSize,而是用AnimatedContainer主动做宽高过渡,不管放大还是缩小都有完整动画。
  3. 兼容动态子组件:如果子组件列表发生变化,会重新缓存所有尺寸,保证动画正常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 09:25:25