Flutter应用按书籍分类排序数据时出现卡顿冻结问题
解决Flutter按书籍分类过滤经文时的卡顿问题
核心问题分析
你当前的实现存在几个关键性能瓶颈:
- 每次过滤都从全量700条数据中筛选,即便用
compute,传递整个List<Verse>会带来序列化开销,且客户端过滤效率远低于数据库层面的查询 - UI层嵌套
SingleChildScrollView+shrinkWrap: true的ListView,会强制ListView预计算所有子项高度,过滤后数据量较大时会引发严重UI卡顿 - 过滤逻辑放在UI层,依赖
setState更新状态,易触发不必要的组件重建
解决方案步骤
1. 数据库层直接按书籍查询(最有效优化)
既然已经使用Floor,完全可以把过滤逻辑交给数据库,只查询对应书籍的经文,避免全量加载后再过滤:
修改verses_dao.dart,添加按book查询的方法:
@dao abstract class VersesDao { @Query('SELECT * FROM ${AppConstants.versesTable}') Future<List<Verse>> fetchVerses(); // 新增按书籍编号查询的方法 @Query('SELECT * FROM ${AppConstants.versesTable} WHERE book = :bookNo') Future<List<Verse>> fetchVersesByBook(int bookNo); }
2. 将过滤逻辑移至Bloc层
把数据查询逻辑从UI层迁移到Bloc,让Bloc负责数据状态管理,UI仅做渲染:
在HomeBloc中添加事件和状态处理,示例如下:
// 事件定义 abstract class HomeEvent {} class HomeFetchData extends HomeEvent {} class HomeFilterByBook extends HomeEvent { final int bookNo; HomeFilterByBook(this.bookNo); } // 状态定义 class HomeState { final Status status; final List<Verse> verses; final List<Verse> filteredVerses; // 新增过滤后列表的状态 final List<Book> books; HomeState({ required this.status, required this.verses, required this.filteredVerses, required this.books, }); // 实现copyWith方法用于状态更新 HomeState copyWith({ Status? status, List<Verse>? verses, List<Verse>? filteredVerses, List<Book>? books, }) { return HomeState( status: status ?? this.status, verses: verses ?? this.verses, filteredVerses: filteredVerses ?? this.filteredVerses, books: books ?? this.books, ); } } // Bloc处理逻辑 class HomeBloc extends Bloc<HomeEvent, HomeState> { final VersesDao versesDao; HomeBloc(this.versesDao) : super(HomeState(status: Status.idle, verses: [], filteredVerses: [], books: [])) { on<HomeFetchData>(_fetchData); on<HomeFilterByBook>(_filterByBook); } void _fetchData(HomeFetchData event, Emitter<HomeState> emit) async { emit(state.copyWith(status: Status.inProgress)); final verses = await versesDao.fetchVerses(); final books = await _fetchBooks(); // 替换为你实际获取书籍列表的逻辑 // 默认加载第一本书的内容 final filtered = await versesDao.fetchVersesByBook(books.first.bookNo!); emit(state.copyWith( status: Status.loaded, verses: verses, filteredVerses: filtered, books: books, )); } void _filterByBook(HomeFilterByBook event, Emitter<HomeState> emit) async { emit(state.copyWith(status: Status.inProgress)); final filtered = await versesDao.fetchVersesByBook(event.bookNo); emit(state.copyWith( status: Status.loaded, filteredVerses: filtered, )); } Future<List<Book>> _fetchBooks() async { // 实现你的书籍列表获取逻辑 return []; } }
3. 优化UI结构,避免嵌套滚动卡顿
移除SingleChildScrollView嵌套ListView的结构,改用Column + Expanded让经文列表独立滚动:
修改home_screen.dart的build方法:
@override Widget build(BuildContext context) { return BlocConsumer<HomeBloc, HomeState>( bloc: _bloc, builder: (context, state) { return Scaffold( backgroundColor: Colors.grey, appBar: AppBar( title: const Text(AppConstants.appTitle), ), body: state.status == Status.inProgress ? const ListLoading() : Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (state.books.isNotEmpty) SizedBox( height: 40, child: ListView.builder( scrollDirection: Axis.horizontal, padding: const EdgeInsets.all(5), itemCount: state.books.length, itemBuilder: (context, index) { final Book book = state.books[index]; return BookItem( text: book.title!, isSelected: setBook == index, onPressed: () { setState(() => setBook = index); _bloc.add(HomeFilterByBook(book.bookNo!)); }, ); }, ), ), Expanded( child: state.filteredVerses.isNotEmpty ? ListView.builder( padding: const EdgeInsets.symmetric(horizontal: Sizes.xs), itemCount: state.filteredVerses.length, itemBuilder: (context, index) { return VerseItem( verse: state.filteredVerses[index], ); }, ) : const EmptyState(title: "It's empty here."), ), ], ), ); }, ); }
4. 移除不必要的客户端过滤代码
现在通过数据库直接查询对应书籍的经文,无需再在客户端做过滤,可删除sortVerses、VerseSort和sortVersesByBook相关代码。
额外优化建议
- 如果经文内容较长,可实现分页加载,每次只加载20条,滚动到底部再加载更多,减少单次渲染的组件数量
- 给
Verse的book字段添加索引,加快数据库查询速度:@Entity(tableName: AppConstants.versesTable, indices: [Index(value: ['book'])]) @JsonSerializable() class Verse { // ... 现有字段 }
内容的提问来源于stack exchange,提问作者Jack Siro
相关产品推荐
相关产品推荐

