Flutter如何实现带图片水平列表及单选变色效果
Flutter 水平图片单选列表实现方案
核心实现逻辑分3部分,直接套就能出效果:
- 状态维护:在
StatefulWidget的状态类中定义整型可空变量int? selectedIndex;,用来记录当前选中的条目下标,默认值为null即无选中状态,需要默认选中第一项就赋初始值0。 - 列表配置:使用
ListView.builder时必须指定scrollDirection: Axis.horizontal开启水平滚动,水平列表必须被固定高度的父组件包裹(比如SizedBox设置明确height),否则会触发布局约束报错。 - 交互与样式:每个列表项外层包裹点击监听组件(
GestureDetector/InkWell),点击时调用setState将selectedIndex更新为当前点击项的下标即可自动完成单选切换——重绘时判断当前项下标是否与selectedIndex相等,匹配则加载选中态样式,不匹配则加载默认样式。
可直接运行的完整示例代码:
import 'package:flutter/material.dart'; class HorizontalImageListPage extends StatefulWidget { const HorizontalImageListPage({super.key}); @override State<HorizontalImageListPage> createState() => _HorizontalImageListPageState(); } class _HorizontalImageListPageState extends State<HorizontalImageListPage> { int? selectedIndex; // 替换为实际业务的图片、标题数据即可 final List<Map<String, dynamic>> listData = List.generate( 10, (index) => { "title": "条目${index + 1}", "imageUrl": "https://picsum.photos/200/300?random=$index" }, ); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("水平图片单选列表")), body: SizedBox( height: 180, child: ListView.builder( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 12), itemCount: listData.length, itemBuilder: (context, index) { final isSelected = selectedIndex == index; return GestureDetector( onTap: () { setState(() { selectedIndex = index; }); // 选中后的业务逻辑写在这里 }, child: Container( width: 120, margin: const EdgeInsets.symmetric(horizontal: 6), decoration: BoxDecoration( color: isSelected ? Colors.blue.shade50 : Colors.white, border: Border.all( color: isSelected ? Colors.blue : Colors.grey.shade300, width: isSelected ? 2 : 1, ), borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded( child: ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(7)), child: Image.network( listData[index]["imageUrl"], fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => const Center(child: Icon(Icons.broken_image)), ), ), ), Padding( padding: const EdgeInsets.all(8.0), child: Text( listData[index]["title"], textAlign: TextAlign.center, style: TextStyle( color: isSelected ? Colors.blue : Colors.black87, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ), ), ) ], ), ), ); }, ), ), ); } }
常见踩坑提醒:
- 不要把
selectedIndex定义在itemBuilder的局部作用域内,必须放在State类的顶层作用域,否则点击后状态更新不会全局生效。 - 本地资源图片把
Image.network替换为Image.asset即可,其余交互逻辑无需调整。 - 如果需要点击已选中项取消高亮效果,在onTap回调里加判断:如果当前点击的下标已经等于
selectedIndex,就把selectedIndex赋值为null即可。
内容的提问来源于stack exchange,提问作者Common Sense
相关产品推荐
相关产品推荐

