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

Flutter如何创建包含图片、文字、副文本的可复用横向滚动Widget?

Flutter 可复用横向滚动Widget实现方案

实现思路

  • 滚动容器优先选择ListView并指定滚动方向为横向,如需支持大量数据懒加载用ListView.builder,少量固定数据直接用SingleChildScrollView嵌套Row即可
  • 封装独立的子项组件HorizontalScrollItem,固定包含图片、主文本、副文本三个模块,所有样式和内容支持外部传入
  • 暴露通用配置参数,适配不同业务场景的定制需求

核心代码实现

子项组件代码

class HorizontalScrollItem extends StatelessWidget {
  final String imageUrl;
  final String mainText;
  final String secondaryText;
  final double itemWidth;
  final TextStyle? mainTextStyle;
  final TextStyle? secondaryTextStyle;
  final VoidCallback? onTap;

  const HorizontalScrollItem({
    super.key,
    required this.imageUrl,
    required this.mainText,
    required this.secondaryText,
    required this.itemWidth,
    this.mainTextStyle,
    this.secondaryTextStyle,
    this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onTap,
      child: SizedBox(
        width: itemWidth,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisSize: MainAxisSize.min,
          children: [
            // 图片模块
            ClipRRect(
              borderRadius: BorderRadius.circular(8),
              child: Image.network(
                imageUrl,
                height: itemWidth * 0.6,
                width: itemWidth,
                fit: BoxFit.cover,
              ),
            ),
            const SizedBox(height: 8),
            // 主文本
            Text(
              mainText,
              style: mainTextStyle ?? const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
            ),
            const SizedBox(height: 4),
            // 副文本
            Text(
              secondaryText,
              style: secondaryTextStyle ?? const TextStyle(fontSize: 12, color: Colors.grey),
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
            ),
          ],
        ),
      ),
    );
  }
}

横向滚动容器代码

class HorizontalScrollWidget extends StatelessWidget {
  final List<Map<String, dynamic>> dataList;
  final double itemWidth;
  final double itemSpacing;
  final double containerPadding;
  final Widget? header;
  final Function(int index)? onItemTap;

  const HorizontalScrollWidget({
    super.key,
    required this.dataList,
    required this.itemWidth,
    this.itemSpacing = 12,
    this.containerPadding = 16,
    this.header,
    this.onItemTap,
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        if (header != null)
          Padding(
            padding: EdgeInsets.symmetric(horizontal: containerPadding),
            child: header!,
          ),
        if (header != null) const SizedBox(height: 12),
        SizedBox(
          height: itemWidth * 0.6 + 50, // 图片高度+文本区域预留高度
          child: ListView.separated(
            padding: EdgeInsets.symmetric(horizontal: containerPadding),
            scrollDirection: Axis.horizontal,
            itemCount: dataList.length,
            separatorBuilder: (context, index) => SizedBox(width: itemSpacing),
            itemBuilder: (context, index) {
              final item = dataList[index];
              return HorizontalScrollItem(
                imageUrl: item['imageUrl'],
                mainText: item['mainText'],
                secondaryText: item['secondaryText'],
                itemWidth: itemWidth,
                onTap: onItemTap != null ? () => onItemTap!(index) : null,
              );
            },
          ),
        ),
      ],
    );
  }
}

使用示例

// 页面中直接调用
HorizontalScrollWidget(
  dataList: [
    {
      "imageUrl": "替换为实际图片地址1",
      "mainText": "主文本内容1",
      "secondaryText": "副文本内容1"
    },
    {
      "imageUrl": "替换为实际图片地址2",
      "mainText": "主文本内容2",
      "secondaryText": "副文本内容2"
    },
    // 可继续添加更多数据项
  ],
  itemWidth: 160,
  header: const Text("推荐内容", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
  onItemTap: (index) {
    // 处理子项点击逻辑
  },
)

内容的提问来源于stack exchange,提问作者Pranav Variar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 19:45:04