如何用Flutter TabBar替换分类列表并传值切换TabView内容
问题
原本使用垂直滚动的CategoryTile列表实现分类导航,点击Tile时传递分类名并打开对应页面展示BlogTileList内容。现在需要将分类列表替换为TabBar,页面替换为TabView,要求无需创建多页面,通过选中Tab传递分类名来切换TabView中的内容。现有代码如下:
获取分类列表代码
List<CategoryModel> getCategories(){ List<CategoryModel> category = <CategoryModel>[]; CategoryModel categoryModel = new CategoryModel(); categoryModel.categoryName = "Business"; category.add(categoryModel); categoryModel = new CategoryModel(); categoryModel.categoryName = "Entertainment"; category.add(categoryModel); categoryModel = new CategoryModel(); categoryModel.categoryName = "General"; category.add(categoryModel); categoryModel = new CategoryModel(); categoryModel.categoryName = "Health"; category.add(categoryModel); categoryModel = new CategoryModel(); categoryModel.categoryName = "Science"; category.add(categoryModel); categoryModel = new CategoryModel(); categoryModel.categoryName = "Sports"; category.add(categoryModel); categoryModel = new CategoryModel(); categoryModel.categoryName = "Technology"; category.add(categoryModel); categoryModel = new CategoryModel(); return category; }
CategoryTile组件
class CategoryTile extends StatelessWidget { final categoryName; CategoryTile({this.categoryName}); @override Widget build(BuildContext context) { final vm = Provider.of<NewsArticleListViewModel>(context); return GestureDetector( onTap: () { vm.Category(categoryName.toLowerCase()); print('tap'); }, child: Container( alignment: Alignment.center, width: 120, height: 60, decoration: BoxDecoration( borderRadius: BorderRadius.circular(6), color: Colors.black26, ), child: Text(categoryName, style: TextStyle( color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500)), ), ); } }
分类页面内容展示代码
Container( margin: const EdgeInsets.only(top: 16), child: ListView.builder( itemCount: articles.length, shrinkWrap: true, physics: ClampingScrollPhysics(), itemBuilder: (context, index) { return BlogTileList( imageUrl: articles[index].urlToImage ?? '', title: articles[index].title ?? '', desc: articles[index].description ?? '', url: articles[index].url ?? '', ); }), )
BlogTileList组件
class BlogTileList extends StatelessWidget { final String imageUrl, title, desc, url; BlogTileList( {required this.imageUrl, required this.title, required this.desc, required this.url}); @override Widget build(BuildContext context) { return GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ArticleView( blogUrl: url, ))); }, child: Padding( padding: const EdgeInsets.symmetric(vertical: 6.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Expanded( flex: 2, child: ClipRRect( child: Container( padding: EdgeInsets.only(right: 10), child: Image.network( imageUrl ?? '', width: 100, height: 90, fit: BoxFit.cover, ), ), ), ), Expanded( flex: 3, child: Padding( padding: const EdgeInsets.fromLTRB(5.0, 0.0, 5.0, 0.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Text(title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500), maxLines: 3), const Padding(padding: EdgeInsets.symmetric(vertical: 2.0)), Text(desc, style: TextStyle(fontSize: 12), maxLines: 2), ], ), ), ), ], ), ), ); } }
解决方案
1. 主页面结构改造:用DefaultTabController管理Tab状态
使用DefaultTabController统一管理Tab的选中状态,配合TabBar生成分类标签,TabView复用同一个内容展示组件,通过ViewModel驱动数据更新。
修改后的主页面代码:
class NewsHomePage extends StatefulWidget { @override _NewsHomePageState createState() => _NewsHomePageState(); } class _NewsHomePageState extends State<NewsHomePage> { final List<CategoryModel> categories = getCategories(); @override Widget build(BuildContext context) { final vm = Provider.of<NewsArticleListViewModel>(context); return DefaultTabController( length: categories.length, initialIndex: 0, // 默认选中第一个分类 child: Scaffold( appBar: AppBar( bottom: TabBar( isScrollable: true, // 分类数量多时允许横向滚动 tabs: categories.map((category) { return Tab(text: category.categoryName); }).toList(), onTap: (index) { // 点击Tab时传递分类名给ViewModel加载对应数据 vm.Category(categories[index].categoryName.toLowerCase()); }, ), ), body: TabView( children: categories.map((_) { // 所有Tab共用同一套内容展示结构,数据由ViewModel实时更新 return Container( margin: const EdgeInsets.only(top: 16), padding: const EdgeInsets.symmetric(horizontal: 16), child: ListView.builder( itemCount: vm.articles.length, shrinkWrap: true, physics: const ClampingScrollPhysics(), itemBuilder: (context, index) { return BlogTileList( imageUrl: vm.articles[index].urlToImage ?? '', title: vm.articles[index].title ?? '', desc: vm.articles[index].description ?? '', url: vm.articles[index].url ?? '', ); }, ), ); }).toList(), ), ), ); } }
2. 确保ViewModel能触发UI更新
如果你的ViewModel还没继承ChangeNotifier,需要补上,确保数据更新时通知UI刷新:
class NewsArticleListViewModel extends ChangeNotifier { List<ArticleModel> articles = []; Future<void> Category(String categoryName) async { // 替换为你的实际网络请求逻辑,获取对应分类的文章 articles = await NewsService.fetchArticlesByCategory(categoryName); notifyListeners(); // 通知UI更新数据 } }
3. 移除冗余组件
原CategoryTile组件已被TabBar替代,直接删除即可。
核心逻辑说明
DefaultTabController自动维护Tab的选中索引,无需手动管理;- TabBar的
onTap回调通过索引获取分类名,传递给ViewModel加载对应文章; - TabView的所有子项复用同一套内容结构,数据完全由ViewModel驱动,切换Tab时ViewModel更新
articles列表,所有Tab的内容会同步刷新为当前选中分类的文章,实现无多页面切换的需求。
内容的提问来源于stack exchange,提问作者Aadhil
相关产品推荐
相关产品推荐

