You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 08:21:24