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
相关产品推荐
相关产品推荐

