分页列表 | Flutter & OpenHarmony PC
本文介绍了Flutter中实现分页列表的核心技术与应用场景。主要内容包括:1)基础分页逻辑实现,通过计算页码和索引范围获取当前页数据;2)分页导航功能开发,包括上一页/下一页按钮、页码显示和跳转输入;3)高级企业级应用方案,如动态调整每页项数、智能缓存预加载、无限滚动虚拟列表等优化技术。文章详细展示了代码实现,并强调了状态管理在分页功能中的重要性,为处理大数据集提供了完整的解决方案。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
案例概述
本案例展示如何实现分页列表,支持上一页、下一页、跳转等功能。分页是处理大数据集的常见方案,通过将数据分成多个页面,可以减少内存占用、提高应用性能。在 Flutter 中,实现分页列表需要管理当前页码、每页项数、总页数等状态,并根据这些状态动态生成当前页的数据。
分页列表在电商、内容管理、数据管理等应用中广泛使用。良好的分页实现应该支持多种导航方式(如上一页、下一页、直接跳转)、动态调整每页项数、缓存已加载的页面数据等功能。此外,对于 PC 端应用,还需要考虑响应式设计、键盘导航等因素。
核心概念
1. 分页逻辑与数据计算
分页的核心是根据当前页码和每页项数,计算出当前页应该显示的数据范围。总页数的计算公式是 (总项数 / 每页项数).ceil(),这确保了即使最后一页的项数不足每页项数,也会被计为一页。当前页的数据范围可以通过起始索引和结束索引来确定,起始索引为 (当前页码 - 1) * 每页项数,结束索引为 起始索引 + 每页项数。
2. 分页导航与用户交互
分页导航提供了多种方式让用户在页面之间切换。最基础的是上一页和下一页按钮,允许用户逐页浏览数据。此外,还可以提供页码显示(如"第 2 页,共 10 页")和直接跳转功能(如输入页码直接跳转)。良好的分页导航设计应该清晰直观,让用户能够快速了解当前位置和总体数据量。
3. 分页状态管理
分页需要维护多个状态变量:当前页码(从 1 开始或从 0 开始,取决于实现)、每页项数、总页数等。此外,还需要根据当前页码判断是否可以进行上一页或下一页操作,以便禁用相应的按钮。这些状态的正确管理是实现可靠分页的关键。
代码详解
1. 基础分页
static const int itemsPerPage = 10;
int _currentPage = 1;
int get _totalPages => (_allItems.length / itemsPerPage).ceil();
List<String> get _currentPageItems {
final startIndex = (_currentPage - 1) * itemsPerPage;
final endIndex = startIndex + itemsPerPage;
return _allItems.sublist(
startIndex,
endIndex > _allItems.length ? _allItems.length : endIndex,
);
}
2. 分页导航
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _currentPage > 1 ? () => setState(() => _currentPage--) : null,
child: Text('上一页'),
),
Text('第 $_currentPage / $_totalPages 页'),
ElevatedButton(
onPressed: _currentPage < _totalPages ? () => setState(() => _currentPage++) : null,
child: Text('下一页'),
),
],
)
3. 页码输入跳转
TextFormField(
decoration: InputDecoration(labelText: '跳转到第几页'),
onFieldSubmitted: (value) {
final page = int.tryParse(value);
if (page != null && page > 0 && page <= _totalPages) {
setState(() => _currentPage = page);
}
},
)
高级话题:分页的企业级应用
1. 动态每页项数
void _changePageSize(int newSize) {
setState(() {
_itemsPerPage = newSize;
_currentPage = 0;
});
}
int get _totalPages => (_data.length / _itemsPerPage).ceil();
List<Map> get _currentPageData {
final start = _currentPage * _itemsPerPage;
final end = (start + _itemsPerPage).clamp(0, _data.length);
return _data.sublist(start, end);
}
Widget _buildPaginator() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.chevron_left),
onPressed: _currentPage > 0 ? () => setState(() => _currentPage--) : null,
),
Text('第 ${_currentPage + 1} 页,共 $_totalPages 页'),
IconButton(
icon: Icon(Icons.chevron_right),
onPressed: _currentPage < _totalPages - 1 ? () => setState(() => _currentPage++) : null,
),
DropdownButton<int>(
value: _itemsPerPage,
items: [5, 10, 20, 50].map((size) {
return DropdownMenuItem(value: size, child: Text('$size 项/页'));
}).toList(),
onChanged: (size) => _changePageSize(size!),
),
],
);
}
2. 智能缓存与预加载
class _PageCache {
final Map<int, List<Map>> _cache = {};
final int _maxCacheSize = 5;
List<Map>? get(int page) => _cache[page];
void set(int page, List<Map> data) {
if (_cache.length >= _maxCacheSize) {
_cache.remove(_cache.keys.first);
}
_cache[page] = data;
}
void clear() => _cache.clear();
}
void _preloadPages() {
// 预加载前后两页
if (_currentPage > 0) {
_loadPage(_currentPage - 1);
}
_loadPage(_currentPage);
if (_currentPage < _totalPages - 1) {
_loadPage(_currentPage + 1);
}
}
Future<void> _loadPage(int page) async {
if (_pageCache.get(page) != null) return;
final start = page * _itemsPerPage;
final end = (start + _itemsPerPage).clamp(0, _data.length);
final pageData = _data.sublist(start, end);
_pageCache.set(page, pageData);
}
3. 无限滚动与虚拟列表
void _loadMore() {
if (_currentPage < _totalPages - 1) {
setState(() => _currentPage++);
} else if (_hasMoreData) {
// 从服务器加载更多数据
_fetchMoreData();
}
}
Future<void> _fetchMoreData() async {
setState(() => _isLoading = true);
try {
final newData = await _api.fetchData(offset: _data.length);
setState(() {
_data.addAll(newData);
_isLoading = false;
});
} catch (e) {
setState(() {
_error = e.toString();
_isLoading = false;
});
}
}
// 虚拟列表实现
ListView.builder(
itemCount: _currentPageData.length,
itemBuilder: (context, index) {
return ListTile(title: Text(_currentPageData[index]['name']));
},
)
4. 搜索、过滤与排序
List<Map> get _filteredData {
var result = _data.where((item) {
final matchesSearch = _searchQuery.isEmpty ||
item['name'].toLowerCase().contains(_searchQuery.toLowerCase());
final matchesFilter = _selectedFilter == null ||
item['status'] == _selectedFilter;
return matchesSearch && matchesFilter;
}).toList();
// 应用排序
if (_sortBy != null) {
result.sort((a, b) {
final aVal = a[_sortBy];
final bVal = b[_sortBy];
return _sortAscending ? aVal.compareTo(bVal) : bVal.compareTo(aVal);
});
}
return result;
}
int get _totalPages => (_filteredData.length / _itemsPerPage).ceil();
List<Map> get _currentPageData {
final start = _currentPage * _itemsPerPage;
final end = (start + _itemsPerPage).clamp(0, _filteredData.length);
return _filteredData.sublist(start, end);
}
5. 响应式分页设计
Widget _buildResponsivePaginator() {
final width = MediaQuery.of(context).size.width;
final isWideScreen = width > 1200;
final isTablet = width > 600 && width <= 1200;
if (isWideScreen) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildPageSizeSelector(),
_buildPageNavigator(),
_buildPageInfo(),
],
);
} else if (isTablet) {
return Column(
children: [
_buildPageNavigator(),
SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildPageSizeSelector(),
_buildPageInfo(),
],
),
],
);
} else {
return Column(
children: [
_buildPageNavigator(),
SizedBox(height: 8),
_buildPageSizeSelector(),
SizedBox(height: 8),
_buildPageInfo(),
],
);
}
}
6. 键盘导航与快捷键
Focus(
onKey: (node, event) {
if (event.isKeyPressed(LogicalKeyboardKey.arrowRight)) {
if (_currentPage < _totalPages - 1) {
setState(() => _currentPage++);
}
return KeyEventResult.handled;
}
if (event.isKeyPressed(LogicalKeyboardKey.arrowLeft)) {
if (_currentPage > 0) {
setState(() => _currentPage--);
}
return KeyEventResult.handled;
}
if (event.isKeyPressed(LogicalKeyboardKey.home)) {
setState(() => _currentPage = 0);
return KeyEventResult.handled;
}
if (event.isKeyPressed(LogicalKeyboardKey.end)) {
setState(() => _currentPage = _totalPages - 1);
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
},
child: ListView(...),
)
7. 无障碍支持与屏幕阅读器
8. 分页的加载状态
bool _isLoading = false;
Future<void> _loadPage(int page) async {
setState(() => _isLoading = true);
try {
final data = await _fetchPageData(page);
setState(() => _currentPageItems = data);
} finally {
setState(() => _isLoading = false);
}
}
9. 分页的错误处理
if (_hasError) {
return Center(
child: Column(
children: [
Text('加载失败'),
ElevatedButton(
onPressed: () => _loadPage(_currentPage),
child: Text('重试'),
),
],
),
);
}
10. 分页的测试
test('分页计算', () {
expect(_totalPages, 5);
expect(_currentPageItems.length, 10);
_currentPage = 5;
expect(_currentPageItems.length, 10);
});
通过这些企业级技巧,你可以构建出功能完整的分页系统。
更多推荐



所有评论(0)