Flutter:基于Future Builder实现无第三方包的双API分页加载求助
解决方案:无第三方包实现带依赖API的分页加载
核心思路
- 状态管理:通过State变量维护分页核心数据(电影列表、分类列表、当前页码、加载状态、是否还有更多数据),避免FutureBuilder重复发起请求。
- 依赖API处理:先加载分类列表(仅一次,作为基础数据),再加载电影分页数据,满足API依赖关系。
- 滚动分页触发:监听ScrollController,滚动到底部时自动加载下一页。
- 加载状态提示:在列表底部动态显示加载指示器。
完整代码实现
1. State类状态变量定义
class _YourScreenState extends State<YourScreen> { List<Movies?> _movies = []; List<Genres?> _genres = []; int _currentPage = 1; bool _isLoading = false; bool _hasMore = true; final ScrollController _scrollController = ScrollController(); @override void initState() { super.initState(); _fetchInitialData(); _scrollController.addListener(_onScroll); } @override void dispose() { _scrollController.dispose(); super.dispose(); }
2. 滚动监听与分页触发
void _onScroll() { // 滚动到底部、未在加载、还有更多数据时触发下一页加载 if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent && !_isLoading && _hasMore) { _loadNextPage(); } }
3. 初始数据加载(处理API依赖)
Future<void> _fetchInitialData() async { _isLoading = true; setState(() {}); try { // 先加载分类列表(依赖项,仅加载一次) final genres = await RepositoryFromAPItoDB().getAllGenres(); // 再加载第一页电影数据 final (movies, totalPages) = await RepositoryFromAPItoDB().getMoviesByPage(_currentPage); _genres = genres; _movies = movies; _hasMore = _currentPage < totalPages; // 根据API返回的总页数判断是否还有更多 } catch (e) { print('初始数据加载失败: $e'); // 可添加错误提示UI,比如SnackBar } finally { _isLoading = false; setState(() {}); } }
4. 下一页数据加载
Future<void> _loadNextPage() async { _isLoading = true; setState(() {}); try { _currentPage++; final (nextPageMovies, totalPages) = await RepositoryFromAPItoDB().getMoviesByPage(_currentPage); if (nextPageMovies.isNotEmpty) { _movies.addAll(nextPageMovies); _hasMore = _currentPage < totalPages; } else { _hasMore = false; } } catch (e) { print('下一页加载失败: $e'); _currentPage--; // 加载失败,回退页码 // 可添加错误提示UI } finally { _isLoading = false; setState(() {}); } }
5. UI构建(含加载提示)
@override Widget build(BuildContext context) { // 初始加载显示全屏加载指示器 if (_movies.isEmpty && _isLoading) { return const Center(child: CircularProgressIndicator()); } return ListView.builder( controller: _scrollController, // 列表项数量:现有电影数 + 加载状态项(如果正在加载) itemCount: _movies.length + (_isLoading ? 1 : 0), itemBuilder: (context, index) { // 最后一项显示加载指示器 if (index == _movies.length) { return const Padding( padding: EdgeInsets.symmetric(vertical: 16), child: Center(child: CircularProgressIndicator()), ); } final movie = _movies[index]; // 传递电影和分类数据到列表项组件 return MoviesListTile(movie: movie, genres: _genres); }, ); } }
6. 修改API支持分页
更新Repository方法,添加分页参数并返回总页数(适配大多数电影API的分页逻辑):
class RepositoryFromAPItoDB { // 带分页的电影请求 Future<(List<Movies?>, int)> getMoviesByPage(int page) async { Response response = await Dio().get( Constants().moviesURL, queryParameters: {'page': page}, // 传递分页参数 ); final movies = (response.data['results'] as List) .map((movieJson) => Movies.fromJson(movieJson)) .toList(); final totalPages = response.data['total_pages'] as int; return (movies, totalPages); } // 分类请求保持不变(仅加载一次) Future<List<Genres?>> getAllGenres() async { Response response = await Dio().get(Constants().genresURL); return (response.data['genres'] as List) .map((genreJson) => Genres.fromJson(genreJson)) .toList(); } }
关键说明
- API依赖调整:如果你的依赖关系是「电影API需要分类ID作为参数」,只需修改
_fetchInitialData,先选择分类后再根据分类ID加载对应电影的分页数据即可。 - 分页判断逻辑:通过API返回的
total_pages判断是否还有更多数据,比依赖每页数量更可靠。 - 错误处理:加载失败时回退页码,避免后续请求错误页码;可根据需求添加用户可见的错误提示。
内容的提问来源于stack exchange,提问作者GrandMagus
相关产品推荐
相关产品推荐

