Flutter 如何通过 ListView.builder 生成样式统一内容唯一的 Card 卡片?
现有逻辑问题
你当前的代码逻辑确实存在问题:VideoCard组件内的图片资源、标题、难度、时长、点击跳转逻辑全部为硬编码的固定值,且ListView.builder循环时仅传入统一的long: true参数,没有给每个卡片传入不同的业务数据,自然所有卡片的内容都完全一致。
解决方案
你可以通过「数据模型封装+组件参数化改造」的方式实现需求,既保证所有卡片UI样式完全统一,又可以灵活配置每个卡片的内容和交互:
- 首先定义视频业务数据模型,统一管理所有可变字段
// 视频数据模型 class VideoModel { final String imagePath; final String title; final String difficulty; final String duration; final VoidCallback onTap; const VideoModel({ required this.imagePath, required this.title, required this.difficulty, required this.duration, required this.onTap, }); }
- 改造
VideoCard组件,所有可变内容改为从参数接收
class VideoCard extends material.StatelessWidget { final bool long; // 新增接收业务数据的参数 final VideoModel videoData; const VideoCard({ @material.required this.long, @material.required this.videoData, material.Key? key, }) : super(key: key); @override material.Widget build(material.BuildContext context) { return material.Padding( padding: const material.EdgeInsets.all(10.0), child: CardWidget( gradient: false, button: true, width: long ? 360 : 180, child: material.Column( mainAxisAlignment: material.MainAxisAlignment.start, children: <material.Widget>[ material.Container( width: long ? 360 : 180, height: 90, decoration: material.BoxDecoration( image: material.DecorationImage( // 使用传入的图片路径 image: material.AssetImage(videoData.imagePath), fit: material.BoxFit.contain), borderRadius: material.BorderRadius.only( topLeft: material.Radius.circular(10), topRight: material.Radius.circular(10), ), ), ), material.Padding( padding: const material.EdgeInsets.all(8.0), child: material.Text( // 使用传入的标题 videoData.title, overflow: material.TextOverflow.ellipsis, maxLines: 2, style: material.TextStyle( color: material.Colors.white, fontFamily: 'Red Hat Display', backgroundColor: material.Colors.black, fontSize: 16), ), ), material.Padding( padding: const material.EdgeInsets.symmetric(horizontal: 8.0), child: material.Row( children: <material.Widget>[ material.Icon(BoxIcons.bx_bar_chart_alt_2, size: 16), material.Text( // 使用传入的难度 videoData.difficulty, style: material.TextStyle( color: material.Color(0xFFADADAD), fontFamily: 'Red Hat Display', fontSize: 10), ), material.Spacer(), material.Text( // 使用传入的时长 videoData.duration, style: material.TextStyle( color: material.Color(0xFFADADAD), fontFamily: 'Red Hat Display', fontSize: 10), ), material.Icon(BoxIcons.bx_timer, size: 16), ], ), ), material.Spacer(), material.Padding( padding: const material.EdgeInsets.only(top: 6.0), child: material.GestureDetector( // 使用传入的点击回调 onTap: videoData.onTap, child: material.Container( padding: material.EdgeInsets.fromLTRB(0, 14, 0, 14), decoration: material.BoxDecoration(gradient: Colors().waves), child: material.Row( mainAxisAlignment: material.MainAxisAlignment.spaceEvenly, children: <material.Widget>[ material.Icon(BoxIcons.bx_play_circle, color: material.Colors.black), material.Text( "Learn it", style: material.TextStyle( color: material.Colors.black, fontFamily: 'Red Hat Display', fontSize: 18), ) ], ), ), ), ), ], ), ), ); } }
- 准备自定义数据源,改造
home.dart中的列表构建逻辑
// 在home.dart中提前定义所有卡片的业务数据 final List<VideoModel> videoList = [ VideoModel( imagePath: 'assets/images/bitcoin.png', title: 'BITCOIN - A pioneer in Crypto!', difficulty: 'Beginner', duration: '10 mins', onTap: () { material.Navigator.push( context, CupertinoPageRoute( builder: (context) => VideoPage(), ), ); }, ), // 按照相同格式添加剩余9个卡片的自定义数据,onTap逻辑可单独配置 VideoModel( imagePath: 'assets/images/ethereum.png', title: 'ETHEREUM - 智能合约首选平台', difficulty: 'Intermediate', duration: '15 mins', onTap: () { // 可自定义跳转逻辑 material.Navigator.push( context, CupertinoPageRoute( builder: (context) => EthVideoPage(), ), ); }, ), // 其余卡片数据以此类推 ]; // 改造原有的ListView.builder代码 SliverToBoxAdapter( child: Container( width: MediaQuery.of(context).size.width, height: 245, child: ListView.builder( scrollDirection: Axis.horizontal, // 直接用数据源长度作为itemCount,无需写死 itemCount: videoList.length, itemBuilder: (context, index) { return VideoCard( long: true, // 传入对应索引的业务数据 videoData: videoList[index], ); } ), ), ),
这种实现方式把UI层和数据层完全分离,卡片的样式统一由VideoCard管控,内容和交互完全由数据源控制,后续新增/修改卡片内容只需要调整videoList即可,不需要修改组件代码,符合Flutter的组件封装规范。
内容的提问来源于stack exchange,提问作者bitboyrd
相关产品推荐
相关产品推荐

