BlocBuilder二次不重建:电影列表无限滚动分页UI不更新
问题分析与解决方案
你的核心问题在于Bloc状态更新时没有合并新旧分页数据,同时UI层的加载状态处理和列表计数存在疏漏,导致新数据无法实时刷新到UI。以下是具体修复步骤:
1. 修复SearchMovieBloc的状态更新逻辑
当前Bloc在请求成功时直接用新页数据替换了原有movies列表,且未在请求开始时设置加载状态。修改后的代码如下:
@injectable class SearchMovieBloc extends Bloc<SearchMovieEvent, SearchMovieState> { final SearchMovieRepo searchmovierepo; SearchMovieBloc(this.searchmovierepo) : super(SearchMovieState.initial()) { on<SearchMovieEvent>((event, emit) async { // 请求开始时,先emit加载状态 emit(state.copyWith(isLoading: true)); final Either<MainFailures, List<MovieModel>> result = await searchmovierepo.searchmovie( moviequery: event.moviequery, page: event.page); log(result.toString()); emit(result.fold( (failure) => state.copyWith(isLoading: false, options: Some(Left(failure))), (newMovies) => state.copyWith( isLoading: false, // 合并原有数据和新分页数据,而非直接替换 movies: [...state.movies, ...newMovies], options: Some(Right(newMovies))))); }); } }
关键改动:
- 请求发起时立即设置
isLoading: true,让UI同步显示加载状态 - 成功时用展开运算符
[...state.movies, ...newMovies]合并新旧列表,保证分页数据累加
2. 优化UI层的无限滚动逻辑
当前UI存在滚动监听重复触发请求、列表未预留加载更多item位置的问题。修改后的_SearchMovieListState代码如下:
class _SearchMovieListState extends State<SearchMovieList> { int page = 1; ScrollController controller = ScrollController(); bool _isLoadingMore = false; // 新增标记,防止重复请求 @override void initState() { controller.addListener(() { if (controller.position.maxScrollExtent == controller.offset && !_isLoadingMore) { _isLoadingMore = true; // 标记为正在加载,避免重复触发 page++; BlocProvider.of<SearchMovieBloc>(context).add( SearchMovieEvent.searchmovie( moviequery: widget.text.text, page: page)); } }); super.initState(); } @override void dispose() { controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return BlocBuilder<SearchMovieBloc, SearchMovieState>( builder: (context, state) { // 同步更新加载标记 _isLoadingMore = state.isLoading; if (state.isLoading && state.movies.isEmpty) { return const Center( child: CircularProgressIndicator( color: orange, ), ); } else if (state.movies.isEmpty && widget.text.text.isNotEmpty) { return const Center( child: Text( "No Results to Show", style: TextStyle(color: grey, fontSize: 25), ), ); } else if (state.movies.isNotEmpty && widget.text.text.isNotEmpty) { return ListView.separated( controller: controller, keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, physics: const BouncingScrollPhysics(), shrinkWrap: true, itemBuilder: ((context, itemIndex) { if (itemIndex < state.movies.length) { return SearchTile( ismovie: true, overview: state.movies[itemIndex].overview!, id: state.movies[itemIndex].movieid!, heading: state.movies[itemIndex].title == null ? state.movies[itemIndex].name! : state.movies[itemIndex].title!, rating: state.movies[itemIndex].rating!.toStringAsFixed(1), image: state.movies[itemIndex].posterPath != null ? "$posterhead${state.movies[itemIndex].posterPath}" : null, year: state.movies[itemIndex].releasedate == null || state.movies[itemIndex].releasedate!.length < 5 ? "_" : state.movies[itemIndex].releasedate! .substring(0, 4)); } else { return const Center( child: CircularProgressIndicator(color: orange)); } }), separatorBuilder: (context, index) => const Divider( height: 4.0, ), // 列表计数加1,预留加载更多的item位置 itemCount: state.movies.length + (state.isLoading ? 1 : 0)); } else { return const SizedBox(); } }, ); } }
关键改动:
- 新增
_isLoadingMore标记,防止滚动到底部时重复发送请求 - 修改
itemCount为state.movies.length + (state.isLoading ? 1 : 0),让加载更多的item能够显示 - 在BlocBuilder中同步更新
_isLoadingMore状态,确保请求结束后能继续触发下一页加载
3. 补充说明
- Freezed生成的状态类是不可变的,必须通过
copyWith创建新状态,合并列表时要生成新的List实例(不能直接修改原列表) - 修改后滚动到底部时,Bloc会合并新数据并emit新状态,BlocBuilder会自动重建UI,无需热重载即可显示新数据
内容的提问来源于stack exchange,提问作者Sh'Jil
相关产品推荐
相关产品推荐

