Flutter如何实现选集组件

在Flutter中,我想实现一个类似视频App中的选集组件,用户可以横向滑动选择不同的剧集。目前尝试了ListView.builder横向布局,但滑动不够流畅,且选集之间的间距和选中状态样式不太理想。请问有没有成熟的实现方案或推荐的三方库?最好能支持以下功能:1. 滑动时自动吸附到居中项;2. 选中项高亮显示;3. 支持动态加载更多选集。

2 回复

在Flutter中实现选集组件,可使用GridView或ListView.builder。通过自定义Item布局,添加选中状态管理,使用setState更新选中项。示例代码可参考官方文档或社区教程。

更多关于Flutter如何实现选集组件的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter中实现选集组件,可以通过以下方式:

1. 基础实现方案

使用WrapListView配合GestureDetector实现:

class EpisodeSelector extends StatefulWidget {
  @override
  _EpisodeSelectorState createState() => _EpisodeSelectorState();
}

class _EpisodeSelectorState extends State<EpisodeSelector> {
  int selectedEpisode = 1;
  final int totalEpisodes = 24;

  @override
  Widget build(BuildContext context) {
    return Wrap(
      spacing: 8.0,
      runSpacing: 8.0,
      children: List.generate(totalEpisodes, (index) {
        final episodeNumber = index + 1;
        final isSelected = episodeNumber == selectedEpisode;
        
        return GestureDetector(
          onTap: () {
            setState(() {
              selectedEpisode = episodeNumber;
            });
          },
          child: Container(
            width: 50,
            height: 50,
            decoration: BoxDecoration(
              color: isSelected ? Colors.blue : Colors.grey[200],
              borderRadius: BorderRadius.circular(8),
              border: isSelected 
                ? Border.all(color: Colors.blue, width: 2)
                : null,
            ),
            child: Center(
              child: Text(
                '$episodeNumber',
                style: TextStyle(
                  color: isSelected ? Colors.white : Colors.black,
                  fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
                ),
              ),
            ),
          ),
        );
      }),
    );
  }
}

2. 增强功能版本

class EnhancedEpisodeSelector extends StatefulWidget {
  final int totalEpisodes;
  final ValueChanged<int> onEpisodeSelected;
  final int initialEpisode;

  const EnhancedEpisodeSelector({
    required this.totalEpisodes,
    required this.onEpisodeSelected,
    this.initialEpisode = 1,
  });

  @override
  _EnhancedEpisodeSelectorState createState() => _EnhancedEpisodeSelectorState();
}

class _EnhancedEpisodeSelectorState extends State<EnhancedEpisodeSelector> {
  late int selectedEpisode;

  @override
  void initState() {
    super.initState();
    selectedEpisode = widget.initialEpisode;
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: const EdgeInsets.symmetric(vertical: 8.0),
          child: Text(
            '选集 ($selectedEpisode/${widget.totalEpisodes})',
            style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          ),
        ),
        GridView.builder(
          shrinkWrap: true,
          physics: NeverScrollableScrollPhysics(),
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 6,
            crossAxisSpacing: 8,
            mainAxisSpacing: 8,
            childAspectRatio: 1.0,
          ),
          itemCount: widget.totalEpisodes,
          itemBuilder: (context, index) {
            final episodeNumber = index + 1;
            final isSelected = episodeNumber == selectedEpisode;
            
            return EpisodeItem(
              episodeNumber: episodeNumber,
              isSelected: isSelected,
              onTap: () {
                setState(() {
                  selectedEpisode = episodeNumber;
                });
                widget.onEpisodeSelected(episodeNumber);
              },
            );
          },
        ),
      ],
    );
  }
}

class EpisodeItem extends StatelessWidget {
  final int episodeNumber;
  final bool isSelected;
  final VoidCallback onTap;

  const EpisodeItem({
    required this.episodeNumber,
    required this.isSelected,
    required this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        decoration: BoxDecoration(
          color: isSelected ? Colors.blue : Colors.grey[200],
          borderRadius: BorderRadius.circular(8),
          border: isSelected 
            ? Border.all(color: Colors.blue, width: 2)
            : Border.all(color: Colors.grey[300]),
        ),
        child: Center(
          child: Text(
            '$episodeNumber',
            style: TextStyle(
              color: isSelected ? Colors.white : Colors.black87,
              fontWeight: FontWeight.w500,
            ),
          ),
        ),
      ),
    );
  }
}

3. 使用方法

// 在页面中使用
EnhancedEpisodeSelector(
  totalEpisodes: 36,
  initialEpisode: 1,
  onEpisodeSelected: (episode) {
    print('选择了第 $episode 集');
    // 处理选集逻辑
  },
)

主要特点:

  • 网格布局展示剧集
  • 选中状态高亮显示
  • 点击回调处理选集逻辑
  • 可自定义样式和布局

可以根据实际需求调整颜色、间距、布局方式等样式参数。

回到顶部