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

Flutter初学者求教:如何实现亚马逊UI的按钮展开动画?

实现点击展开的动画布局(仿亚马逊UI)

要实现你要的点击按钮后下移并展开新组件的效果,核心是用**AnimatedContainer配合Wrap**组件,通过动态改变容器高度实现平滑过渡,同时利用Wrap的自动布局特性让其他元素自动调整位置。

完整代码示例

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text("仿亚马逊分类展开")),
        body: const ExpandableWrapDemo(),
      ),
    );
  }
}

class ExpandableWrapDemo extends StatefulWidget {
  const ExpandableWrapDemo({super.key});

  @override
  State<ExpandableWrapDemo> createState() => _ExpandableWrapDemoState();
}

class _ExpandableWrapDemoState extends State<ExpandableWrapDemo> {
  // 记录每个分类项的展开状态
  final Map<int, bool> _expandedStates = {};

  // 模拟分类数据
  final List<Map<String, dynamic>> _categories = [
    {
      "title": "电子产品",
      "subItems": ["手机", "电脑", "耳机", "平板"]
    },
    {
      "title": "服装",
      "subItems": ["男装", "女装", "童装", "配饰"]
    },
    {
      "title": "家居",
      "subItems": ["家具", "家纺", "厨具", "收纳"]
    },
    {
      "title": "图书",
      "subItems": ["小说", "科技", "人文", "教育"]
    },
    {
      "title": "美妆",
      "subItems": ["护肤", "彩妆", "香水", "工具"]
    },
  ];

  // 切换展开状态
  void _toggleExpand(int index) {
    setState(() {
      _expandedStates[index] = !(_expandedStates[index] ?? false);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Wrap(
        spacing: 8.0, // 水平间距
        runSpacing: 8.0, // 垂直间距
        children: List.generate(_categories.length, (index) {
          final category = _categories[index];
          final isExpanded = _expandedStates[index] ?? false;
          return ExpandableItem(
            title: category["title"],
            isExpanded: isExpanded,
            onTap: () => _toggleExpand(index),
            subItems: category["subItems"],
          );
        }),
      ),
    );
  }
}

class ExpandableItem extends StatelessWidget {
  final String title;
  final bool isExpanded;
  final VoidCallback onTap;
  final List<String> subItems;

  const ExpandableItem({
    super.key,
    required this.title,
    required this.isExpanded,
    required this.onTap,
    required this.subItems,
  });

  @override
  Widget build(BuildContext context) {
    const collapsedHeight = 48.0;
    // 展开高度可根据子项数量动态计算,这里用固定值简化示例
    final expandedHeight = collapsedHeight + 100.0;

    return AnimatedContainer(
      duration: const Duration(milliseconds: 300),
      curve: Curves.easeInOut,
      height: isExpanded ? expandedHeight : collapsedHeight,
      width: 120.0,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey.shade300),
        borderRadius: BorderRadius.circular(8.0),
      ),
      child: Column(
        children: [
          // 顶部可点击按钮
          GestureDetector(
            onTap: onTap,
            child: Container(
              height: collapsedHeight,
              alignment: Alignment.center,
              child: Text(
                title,
                textAlign: TextAlign.center,
                style: const TextStyle(fontSize: 14),
              ),
            ),
          ),
          // 展开时显示的子内容
          if (isExpanded)
            Expanded(
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: GridView.count(
                  crossAxisCount: 2,
                  shrinkWrap: true,
                  mainAxisSpacing: 4.0,
                  crossAxisSpacing: 4.0,
                  children: subItems.map((item) {
                    return Text(
                      item,
                      textAlign: TextAlign.center,
                      style: TextStyle(fontSize: 12, color: Colors.grey.shade600),
                    );
                  }).toList(),
                ),
              ),
            ),
        ],
      ),
    );
  }
}

关键实现说明

  • AnimatedContainer:通过动态修改height属性实现平滑的高度过渡动画,可通过duration和curve调整动画的速度和过渡曲线。
  • Wrap组件:自动流式排列子元素,当某个项展开高度增加时,Wrap会自动将下方的元素下移,实现你要的布局调整效果。
  • 状态管理:用Map<int, bool>记录每个项的展开状态,点击按钮时切换状态并触发组件重绘。
  • 子内容布局:展开后的子项用GridView.count实现多列排列,也可根据需求换成Wrap或其他布局组件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 07:07:40