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

Flutter水平ListView堆叠单元格点击展开动画实现方案咨询

实现堆叠单元格展开为水平ListView的动画方案

核心思路

通过状态控制切换两种布局:堆叠带角度的单元格(未展开状态)和水平ListView(展开状态),同时用动画过渡旋转角度,让展开/收起过程更流畅自然。

具体实现步骤

  • 维护展开状态:用StatefulWidget保存_isExpanded布尔值,标记当前项的展开状态。
  • 添加动画控制器:创建AnimationController和对应Tween,控制每个单元格从原旋转角度过渡到无旋转状态(展开时),或反向过渡(收起时)。
  • 布局切换逻辑:根据_isExpanded的值,渲染堆叠布局或水平ListView布局。
  • 绑定点击交互:给整个项添加GestureDetector,点击时切换展开状态并触发动画。

代码示例(基于你的现有代码修改)

class ExpandableBookSeries extends StatefulWidget {
  final List<Book> seriesBooks;

  const ExpandableBookSeries({super.key, required this.seriesBooks});

  @override
  State<ExpandableBookSeries> createState() => _ExpandableBookSeriesState();
}

class _ExpandableBookSeriesState extends State<ExpandableBookSeries> with SingleTickerProviderStateMixin {
  late AnimationController _animationController;
  late List<Animation<double>> _rotationAnimations;
  bool _isExpanded = false;

  @override
  void initState() {
    super.initState();
    // 初始化动画控制器,时长可按需调整
    _animationController = AnimationController(
      duration: const Duration(milliseconds: 400),
      vsync: this,
    );
    // 为每个单元格创建旋转动画:从原角度过渡到0(无旋转)
    const startAngle = -3.0;
    const endAngle = 3.0;
    final step = (endAngle - startAngle) / (widget.seriesBooks.length - 1);
    _rotationAnimations = List.generate(widget.seriesBooks.length, (index) {
      final originalAngle = (startAngle + step * index) * (pi / 180);
      return Tween<double>(begin: originalAngle, end: 0.0).animate(
        CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
      );
    });
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

  void _toggleExpand() {
    setState(() {
      _isExpanded = !_isExpanded;
    });
    // 根据展开状态执行动画
    _isExpanded ? _animationController.forward() : _animationController.reverse();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _toggleExpand,
      child: _isExpanded ? _buildExpandedListView() : _buildStackedBooks(),
    );
  }

  Widget _buildStackedBooks() {
    return Stack(
      children: List.generate(widget.seriesBooks.length, (index) {
        return Positioned.fill(
          child: AnimatedBuilder(
            animation: _rotationAnimations[index],
            builder: (context, child) {
              return Transform.rotate(
                angle: _rotationAnimations[index].value,
                child: _buildCell(widget.seriesBooks[index]),
              );
            },
          ),
        );
      }),
    );
  }

  Widget _buildExpandedListView() {
    return SizedBox(
      height: 200, // 按需设置单元格高度
      child: ListView.builder(
        scrollDirection: Axis.horizontal,
        itemCount: widget.seriesBooks.length,
        itemBuilder: (context, index) {
          return AnimatedBuilder(
            animation: _rotationAnimations[index],
            builder: (context, child) {
              return Transform.rotate(
                angle: _rotationAnimations[index].value,
                child: _buildCell(widget.seriesBooks[index]),
              );
            },
          );
        },
      ),
    );
  }

  Widget _buildCell(Book book) {
    return Stack(
      children: [
        Image(image: AssetImage('assets/cover_${book.id}.png')),
        const Positioned(
          top: 10,
          right: 10,
          child: Image(image: AssetImage('assets/start.png')),
        ),
      ],
    );
  }
}

额外优化建议

  • 添加平移动画:如果希望展开时单元格有从堆叠位置到ListView位置的平移效果,可以给每个单元格添加Transform.translate的动画,让过渡更自然。
  • 调整动画曲线:根据需求替换Curves,比如用Curves.elasticOut让展开动作带弹性效果。
  • 独立单元格交互:展开后若需要每个单元格独立响应点击,要调整GestureDetector的层级,避免父容器拦截事件。
  • 性能优化:用AnimatedBuilder包裹动画组件,减少不必要的重绘。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 17:15:05