You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 12:14:51