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
相关产品推荐
相关产品推荐

