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

Flutter如何将长ListView分割为多页面并支持搜索过滤?

Flutter 分页+搜索列表实现方案

首先将原无状态组件调整为有状态组件,维护页码、搜索关键词等状态,先执行全量数据的搜索过滤,再基于过滤结果做分页切割即可满足需求。完整实现代码如下:

import 'dart:math';
import 'package:flutter/material.dart';

// 你可以把常量放到单独的配置文件中
const kBackgroundColor = Colors.white;
const kPrimaryColor = Colors.blue;

class SongList extends StatefulWidget {
  @override
  State<SongList> createState() => _SongListState();
}

class _SongListState extends State<SongList> {
  // 分页配置
  final int _pageSize = 100;
  int _currentPage = 1;
  // 搜索配置
  String _searchKeyword = '';
  // 原始700条数据源,后续可替换为接口请求/数据库查询的真实数据
  final List<Map<String, dynamic>> _originalSongs = List.generate(700, (index) => {
    'id': index,
    'name': 'Song #${index + 1}'
  });

  @override
  Widget build(BuildContext context) {
    // 1. 先按搜索关键词过滤全量数据
    final List<Map<String, dynamic>> _filteredSongs = _originalSongs.where((song) {
      if (_searchKeyword.isEmpty) return true;
      return song['name']!.toLowerCase().contains(_searchKeyword.toLowerCase());
    }).toList();

    // 2. 计算总页数、当前页的起止索引
    final int _totalPages = (_filteredSongs.length / _pageSize).ceil();
    final int _startIndex = (_currentPage - 1) * _pageSize;
    final int _endIndex = min(_startIndex + _pageSize, _filteredSongs.length);
    // 当前页要显示的列表数据
    final List<Map<String, dynamic>> _currentPageSongs = _filteredSongs.isEmpty ? [] : _filteredSongs.sublist(_startIndex, _endIndex);

    return Scaffold(
      appBar: AppBar(
        title: const Text('歌曲列表'),
        // 搜索栏
        bottom: PreferredSize(
          preferredSize: const Size.fromHeight(48),
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
            child: TextField(
              decoration: const InputDecoration(
                hintText: '搜索歌曲名称',
                prefixIcon: Icon(Icons.search),
                border: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(24))),
                contentPadding: EdgeInsets.zero
              ),
              onChanged: (value) {
                setState(() {
                  _searchKeyword = value;
                  _currentPage = 1; // 搜索时重置到第一页
                });
              },
            ),
          ),
        ),
      ),
      body: Column(
        children: [
          // 列表内容
          Expanded(
            child: _currentPageSongs.isEmpty
                ? const Center(child: Text('暂无匹配的歌曲'))
                : ListView.separated(
              padding: const EdgeInsets.symmetric(vertical: 8),
              itemCount: _currentPageSongs.length,
              itemBuilder: (_, index) {
                final song = _currentPageSongs[index];
                final count = song['id'] + 1;
                return ListTile(
                  leading: CircleAvatar(
                    child: Text(
                      "$count",
                      style: const TextStyle(color: kBackgroundColor),
                    ),
                    backgroundColor: kPrimaryColor,
                  ),
                  title: Text(song['name']),
                  trailing: const Icon(Icons.navigate_next),
                  onTap: () {
                    Navigator.push(
                      context,
                      MaterialPageRoute(
                        builder: (context) => SongDetail(song['id']),
                      ),
                    );
                  },
                );
              },
              separatorBuilder: (context, index) => const Divider(height: 1),
            ),
          ),
          // 分页导航控件
          if (_filteredSongs.isNotEmpty)
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 8),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: [
                  ElevatedButton(
                    onPressed: _currentPage == 1 ? null : () {
                      setState(() {
                        _currentPage--;
                      });
                    },
                    child: const Text('上一页'),
                  ),
                  Text('第 $_currentPage 页 / 共 $_totalPages 页'),
                  ElevatedButton(
                    onPressed: _currentPage >= _totalPages ? null : () {
                      setState(() {
                        _currentPage++;
                      });
                    },
                    child: const Text('下一页'),
                  ),
                ],
              ),
            )
        ],
      ),
    );
  }
}

// 原有详情页保留即可
class SongDetail extends StatelessWidget {
  final int songId;
  const SongDetail(this.songId, {super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('歌曲详情 #${songId+1}')),
      body: Center(child: Text('这是歌曲 ${songId+1} 的详情页')),
    );
  }
}

可调整说明

  • 搜索匹配规则可自行修改,比如支持拼音搜索、首字母搜索等
  • 分页控件的样式、按钮文案都可以按需调整,也可以替换为第三方分页组件
  • 如果后续数据改为接口分页请求,只需要调整数据请求逻辑,搜索、分页的交互逻辑不需要大幅修改

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 03:09:04