Flutter无限滚动分页插件riverpod_infinite_scroll_pagination的使用
Flutter无限滚动分页插件riverpod_infinite_scroll_pagination的使用
示例代码
import 'package:flutter/material.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:dio/dio.dart';
import 'package:example/src/env/env.dart';
import 'package:example/src/features/movies/data/tmdb_repository.dart';
part 'tmdb_movie.freezed.dart';
part 'tmdb_movie.g.dart';
[@freezed](/user/freezed)
class TmdbMovie with _$TmdbMovie {
factory TmdbMovie({
bool? adult,
@JsonKey(name: 'backdrop_path') String? backdropPath,
int? id,
@JsonKey(name: 'original_language') String? originalLanguage,
@JsonKey(name: 'original_title') String? originalTitle,
String? overview,
double? popularity,
@JsonKey(name: 'poster_path') String? posterPath,
@JsonKey(name: 'release_date') String? releaseDate,
String? title,
bool? video,
@JsonKey(name: 'vote_average') double? voteAverage,
@JsonKey(name: 'vote_count') int? voteCount,
}) = _TmdbMovie;
factory TmdbMovie.fromJson(Map<String, dynamic> json) => _$TmdbMovieFromJson(json);
}
class TmdbRepository {
const TmdbRepository({
required this.dio,
});
final Dio dio;
Future<PaginatedResponse<TmdbMovie>> getTrendingMovies({
int page = 1,
String? query,
}) async {
final results = await dio.get<Map<String, dynamic>>({
'trendin/movie/day?language=en-US&'page=$page${query != null ? '&$query' : ''}',
});
return PaginatedResponse.fromJson(
results.data!,
dataMapper: TmdbMovie.fromJson,
dataField: 'results',
paginationParser: (data) => Pagination(
totalNumber: data['total_results'] as int,
currentPage: data['page'] as int,
lastPage: data['total_pages'] as int,
),
);
}
}
[@riverpod](/user/riverpod)
class TrendingMoviesList extends _$TrendingMoviesList with PaginatedDataMixin<TmdbMovie> implements PaginatedNotifier<TmdbMovie> {
@override
FutureOr<List<TmdbMovie>> build() async {
return init(
dataFetcher: PaginatedDataRepository(
fetcher: ref.watch(tmdbRepositoryProvider).getTrendingMovies,
),
);
}
}
class MovieList extends ConsumerWidget {
const MovieList({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final movies = ref.watch(trendingMoviesListProvider);
return Scaffold(
appBar: AppBar(title: const Text('Trending Movies')),
body: PaginatedListView(
state: movies,
itemBuilder: (_, data) => MovieItem(movie: data),
notifier: ref.read(trendingMoviesListProvider.notifier),
),
);
}
}
使用说明
-
初始化数据源
- 首先,我们需要初始化一个
AsyncNotifier
来管理状态。在这个例子中,我们使用了TrendingMoviesList
提供器来获取 trending movies 的列表。
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:example/src/features/movies/providers/trending_movies_list_provider.dart'; void main() { runApp(RiverpodApp()); } class RiverpodApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: MyHomePage(), ); } } class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Trending Movies')), body: Provider( child: MovieList(), ), ); } }
- 首先,我们需要初始化一个
-
创建模型类和仓库
- 我们使用
freezed
包来创建电影模型类,并使用dio
库来处理网络请求。
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:dio/dio.dart'; import 'package:example/src/features/movies/data/tmdb_repository.dart'; part 'tmdb_movie.freezed.dart'; part 'tmdb_movie.g.dart'; [@freezed](/user/freezed) class TmdbMovie with _$TmdbMovie { factory TmdbMovie({ bool? adult, @JsonKey(name: 'backdrop_path') String? backdropPath, int? id, @JsonKey(name: 'original_language') String? originalLanguage, @JsonKey(name: 'original_title') String? originalTitle, String? overview, double? popularity, @JsonKey(name: 'poster_path') String? posterPath, @JsonKey(name: 'release_date') String? releaseDate, String? title, bool? video, @JsonKey(name: 'vote_average') double? voteAverage, @JsonKey(name: 'vote_count') int? voteCount, }) = _TmdbMovie; factory TmdbMovie.fromJson(Map<String, dynamic> json) => _$TmdbMovieFromJson(json); } class TmdbRepository { const TmdbRepository({ required this.dio, }); final Dio dio; Future<PaginatedResponse<TmdbMovie>> getTrendingMovies({ int page = 1, String? query, }) async { final results = await dio.get<Map<String, dynamic>>({ 'trendin/movie/day?language=en-US&page=$page${query != null ? '&$query' : ''}', }); return PaginatedResponse.fromJson( results.data!, dataMapper: TmdbMovie.fromJson, dataField: 'results', paginationParser: (data) => Pagination( totalNumber: data['total_results'] as int, currentPage: data['page'] as int, lastPage: data['total_pages'] as int, ), ); } }
- 我们使用
-
设置提供器
- 创建
TrendingMoviesList
提供器来初始化TmdbRepository
并进行数据加载。
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:example/src/features/movies/data/tmdb_repository.dart'; part 'tmdb_repository_provider.g.dart'; [@riverpod](/user/riverpod) TmdbRepository tmdbRepository(TmdbRepositoryRef ref) { return TmdbRepository( dio: Dio( BaseOptions( baseUrl: 'https://api.themoviedb.org/3/', headers: <String, dynamic>{ 'Accept': 'application/json', 'Authorization': 'Bearer ${Env.tmdbApiKey}', }, ), ), ); }
- 创建
-
构建 UI
- 在
MovieList
消费者 widget 中使用PaginatedListView
来展示数据。
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class MovieList extends ConsumerWidget { const MovieList({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final movies = ref.watch(trendingMoviesListProvider); return Scaffold( appBar: AppBar(title: const Text('Trending Movies')), body: PaginatedListView( state: movies, itemBuilder: (_, data) => MovieItem(movie: data), notifier: ref.read(trendingMoviesListProvider.notifier), ), ); } }
- 在
-
添加搜索功能
- 如果需要添加搜索功能,可以修改
SearchMovies
提供器来处理查询参数。
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:example/src/features/movies/data/tmdb_repository.dart'; part 'search_movies_provider.g.dart'; [@riverpod](/user/riverpod) class SearchMovies extends _$SearchMovies with PaginatedDataMixin<TmdbMovie> implements PaginatedNotifier<TmdbMovie> { @override FutureOr<List<TmdbMovie>> build() async { final dataFetcher = PaginatedDataRepository( fetcher: ref.watch(tmdbRepositoryProvider).searchMovies, queryFilter: queryFilter, ); return init( dataFetcher: PaginatedDataRepository( fetcher: ref.watch(tmdbRepositoryProvider).searchMovies, queryFilter: queryFilter, ), ); } }
- 如果需要添加搜索功能,可以修改
-
使用 Slivers 和 Skeleton 动画
- 可以通过设置
useSliver
参数为true
来使用 Slivers,并传递一个ScrollController
实例。
- 可以通过设置
更多关于Flutter无限滚动分页插件riverpod_infinite_scroll_pagination的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
1 回复
更多关于Flutter无限滚动分页插件riverpod_infinite_scroll_pagination的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何使用 riverpod_infinite_scroll_pagination
插件来实现 Flutter 应用中的无限滚动分页功能的代码示例。这个插件结合了 Riverpod 状态管理,可以非常方便地实现分页加载数据。
首先,确保在你的 pubspec.yaml
文件中添加以下依赖:
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^1.0.0 # 确保版本与riverpod_infinite_scroll_pagination兼容
riverpod_infinite_scroll_pagination: ^3.0.0 # 使用最新版本
然后运行 flutter pub get
来获取依赖。
接下来,设置你的 Flutter 应用。以下是一个完整的示例,展示如何使用 riverpod_infinite_scroll_pagination
:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_infinite_scroll_pagination/riverpod_infinite_scroll_pagination.dart';
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Infinite Scroll Pagination',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: InfiniteScrollPage(),
);
}
}
final itemsProvider = StateNotifierProvider<ItemsNotifier, List<String>>(
(ref) => ItemsNotifier([]),
);
class ItemsNotifier extends StateNotifier<List<String>> {
ItemsNotifier(List<String> items) : super(items);
void addItems(List<String> newItems) {
state = [...state, ...newItems];
}
}
final paginationProvider = PaginationProvider<int, String>(
key: 'items',
pageKeyExtractor: (item, index) => item, // 用于唯一标识每一项,这里我们假设每一项是一个唯一的int
pageFunction: (pageKey, previousItems, fetchFunction) async {
// 模拟从网络获取数据
await Future.delayed(const Duration(seconds: 2));
final newItems = List.generate(
20,
(index) => 'Item ${(pageKey - 1) * 20 + index + 1}',
);
return PaginationResult.success(data: newItems);
},
itemBuilder: (context, item, index) => ListTile(
title: Text(item),
),
);
class InfiniteScrollPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final itemsNotifier = ref.watch(itemsProvider.notifier);
final paginationController = ref.watch(paginationProvider.controller);
ref.onDispose(() {
paginationController.dispose();
});
return Scaffold(
appBar: AppBar(
title: Text('Infinite Scroll Pagination'),
),
body: NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
if (scrollNotification is ScrollEndNotification &&
scrollNotification.metrics.pixels ==
scrollNotification.metrics.maxScrollExtent) {
// 滚动到底部时加载更多数据
paginationController.nextPage();
}
return true;
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: PaginationView<int, String>(
paginationProvider: paginationProvider,
loadMore: () async {
// 这里可以添加加载更多的逻辑,但paginationProvider已经处理了这部分
// 只是为了演示,这里可以注释或保留为空
},
errorBuilder: (context, error, stackTrace, retry) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('An error occurred: $error'),
SizedBox(height: 12),
ElevatedButton(
onPressed: retry,
child: Text('Retry'),
),
],
),
);
},
emptyListBuilder: (context) {
return Center(
child: Text('No items found'),
);
},
// 将分页结果添加到itemsNotifier中(可选,视需求而定)
onPageLoaded: (pageKey, newItems) {
itemsNotifier.addItems(newItems);
},
),
),
),
);
}
}
解释
- 依赖注入:使用
ProviderScope
包裹整个应用,确保 Riverpod 正常工作。 - 状态管理:使用
StateNotifierProvider
管理项目的列表状态。 - 分页逻辑:使用
PaginationProvider
配置分页逻辑,包括如何获取下一页的数据和如何构建每一项。 - 滚动监听:使用
NotificationListener
监听滚动事件,当滚动到底部时调用paginationController.nextPage()
加载更多数据。 - 分页视图:使用
PaginationView
展示分页数据,并处理错误和空列表的情况。
这个示例演示了如何使用 riverpod_infinite_scroll_pagination
插件结合 Riverpod 实现无限滚动分页。根据实际需求,你可能需要调整分页逻辑、数据获取方式和UI展示。