Flutter如何创建平滑展开的下拉列表?
实现可平滑展开的下拉列表(Flutter)
嘿,作为Flutter新手,你想要的这种可展开收起的列表,用Flutter自带的ExpansionTile组件就能轻松实现——它内置了平滑的展开/收起动画,完全符合你的需求!我会结合你的现有代码,给你改造出可运行的实现方案。
步骤1:定义数据模型
首先,我们需要一个数据模型来对应API返回的分类和子测试数据,这样处理起来更清晰:
// 定义分类模型,包含子测试列表 class PracticeTestCategory { final String title; final int submittedCount; final List<String> children; // 子测试名称列表,可根据实际API结构调整 PracticeTestCategory({ required this.title, required this.submittedCount, required this.children, }); }
步骤2:改造你的状态组件
接下来,修改你的_PracticetestComp类,集成ExpansionTile并处理API数据:
import 'package:flutter/material.dart'; import 'package:lpa_exam/src/api/api_manager.dart'; class Practicetest extends StatefulWidget { final examId; Practicetest({Key? key, this.examId}) : super(key: key); @override _PracticetestComp createState() => _PracticetestComp(); } class _PracticetestComp extends State<Practicetest> { List<PracticeTestCategory>? _practiceTestCategories; bool _isLoading = true; // 新增加载状态标识 @override void initState() { super.initState(); _fetchPracticeTests(); } // 重构API请求方法,增加错误处理和加载状态 Future<void> _fetchPracticeTests() async { try { final result = await APIManager.getpracticetestlist(widget.examId); // 这里根据实际API返回结构,将result转换为PracticeTestCategory列表 // 示例模拟转换(你需要替换成真实的解析逻辑) setState(() { _practiceTestCategories = result.map((item) => PracticeTestCategory( title: item['title'], submittedCount: item['submitted_count'], children: List<String>.from(item['children']), )).toList(); }); } catch (e) { // 这里可以添加错误提示,比如SnackBar print('加载数据失败: $e'); } finally { setState(() { _isLoading = false; }); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xffF8FDF7), appBar: AppBar( backgroundColor: const Color(0xffF8FDF7), brightness: Brightness.light, elevation: 0.0, leading: Container( margin: const EdgeInsets.only(left: 17), child: RawMaterialButton( onPressed: () { Navigator.pushNamed(context, '/'); }, child: const Icon( Icons.keyboard_backspace, color: Colors.redAccent, size: 25.0, ), shape: const CircleBorder(), elevation: 4.0, fillColor: Colors.white, padding: const EdgeInsets.all(5.0), ), ), ), body: Container( margin: const EdgeInsets.only(top: 40, left: 30, right: 30), child: _isLoading ? const Center(child: CircularProgressIndicator()) // 加载中显示指示器 : ListView( shrinkWrap: true, children: [ // 标题部分 const ListTile( dense: true, contentPadding: EdgeInsets.all(0), title: Text( 'Quick set of', style: TextStyle( fontWeight: FontWeight.w500, fontSize: 18, ), ), subtitle: Text( 'Practice Tests', style: TextStyle( fontWeight: FontWeight.w700, fontSize: 22, color: Colors.black), ), ), const SizedBox(height: 30), // 动态生成可展开列表 if (_practiceTestCategories != null) ..._practiceTestCategories!.map((category) { return Column( children: [ ExpansionTile( contentPadding: EdgeInsets.zero, leading: Text( category.title, style: const TextStyle( color: Colors.black, fontWeight: FontWeight.w500, ), ), trailing: Text( '${category.submittedCount} Submitted', style: const TextStyle(color: Colors.grey), ), // 子列表内容,这里用简单的ListTile示例,你可以自定义样式 children: category.children.map((child) { return Padding( padding: const EdgeInsets.only(left: 20), child: ListTile( title: Text(child), onTap: () { // 点击子项的逻辑,比如跳转到测试页面 print('点击了子项: $child'); }, ), ); }).toList(), ), // 每个分类之间的分隔线 const Divider(color: Colors.grey, thickness: 1), ], ); }).toList(), ], ), ), ); } }
关键说明
- ExpansionTile的核心作用:它自动处理展开/收起的平滑动画,你只需要指定
leading(左侧标题)、trailing(右侧状态文本)和children(展开后的子列表)即可。 - 加载状态处理:新增了
_isLoading变量,在API请求完成前显示加载指示器,提升用户体验。 - 数据解析:你需要根据API返回的真实数据结构,调整
_fetchPracticeTests里的模型转换逻辑,确保能正确把API数据转成PracticeTestCategory列表。 - 子项自定义:子列表的样式可以完全自定义,比如替换成卡片、自定义Widget等,满足你的UI需求。
如果需要更定制化的展开动画或样式,也可以自己用AnimatedContainer结合GestureDetector实现,但对于新手来说,ExpansionTile是最快捷的方案!
内容的提问来源于stack exchange,提问作者Gaurav Kumar
相关产品推荐
相关产品推荐

