perf(discover): 剧场与新片页网格改为按行懒加载
flutter-ci / test (push) Failing after 19m39s

双列网格原来是两个 Column 一次性建出所有卡片,翻几页后上百张卡连同封面都留在树上;
现在改成按行的 SliverList,只构建可见行,剧场页/新片页/搜索结果的滚动容器一并换成 sliver。
搜索结果、加载失败等状态同步改成 sliver,滚动位置与下拉刷新行为保持不变。
This commit is contained in:
Vicky authored and trae-solo-agent committed 2026-10-29 11:38:21 +08:00
1 parent 4aaa5bbd60
commit fe49527dd8
6 files changed
+193 -145

No files matched your search

+51 -33
View File
@@ -153,14 +153,19 @@ class _DiscoverPageState extends State<DiscoverPage> {
edgeOffset: _stickyHeaderHeight + 8,
onRefresh: () =>
Future.wait([categories.load(), theater.refreshCurrent()]),
child: ListView(
child: CustomScrollView(
controller: logic.scroll,
physics: AppPullToRefresh.physics,
padding: EdgeInsets.only(
top: showHeader ? _stickyHeaderHeight : 0,
bottom: AppDimens.spaceXl,
),
children: _content(l10n, categories),
slivers: [
if (showHeader)
const SliverToBoxAdapter(
child: SizedBox(height: _stickyHeaderHeight),
),
..._content(l10n, categories),
const SliverToBoxAdapter(
child: SizedBox(height: AppDimens.spaceXl),
),
],
),
),
),
@@ -228,12 +233,12 @@ class _DiscoverPageState extends State<DiscoverPage> {
];
}
if (categories.loading && categories.categories.isEmpty) {
return [_block(const CircularProgressIndicator(strokeWidth: 2))];
return [_sliverBlock(const CircularProgressIndicator(strokeWidth: 2))];
}
final error = categories.error;
if (error != null) {
return [
_block(
_sliverBlock(
EmptyState(
key: const Key('home-categories-error'),
icon: Icons.cloud_off_rounded,
@@ -253,12 +258,12 @@ class _DiscoverPageState extends State<DiscoverPage> {
];
}
if (theater.loadingFirstPage) {
return [_block(const CircularProgressIndicator(strokeWidth: 2))];
return [_sliverBlock(const CircularProgressIndicator(strokeWidth: 2))];
}
final theaterError = theater.lastError;
if (theaterError != null && theater.dramas.isEmpty) {
return [
_block(
_sliverBlock(
EmptyState(
key: const Key('home-categories-error'),
icon: Icons.cloud_off_rounded,
@@ -282,38 +287,49 @@ class _DiscoverPageState extends State<DiscoverPage> {
List<Widget> _theaterBlocks(AppLocalizations l10n) {
final dramas = theater.dramas;
return [
_toolbar(),
SliverToBoxAdapter(child: _toolbar()),
if (dramas.isEmpty)
Padding(
padding: const EdgeInsets.symmetric(vertical: 40),
child: EmptyState(
key: const Key('discover-empty'),
icon: Icons.movie_filter_outlined,
title: l10n.noStories,
subtitle: l10n.emptyHint,
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40),
child: EmptyState(
key: const Key('discover-empty'),
icon: Icons.movie_filter_outlined,
title: l10n.noStories,
subtitle: l10n.emptyHint,
),
),
)
else
DramaGrid(l10n: l10n, dramas: dramas, onOpen: _openDrama),
SliverDramaGrid(
key: const Key('home-all-dramas-grid'),
l10n: l10n,
dramas: dramas,
onOpen: _openDrama,
),
if (theater.loadingMore)
const Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Center(
child: SizedBox.square(
key: Key('theater-load-more'),
dimension: 22,
child: CircularProgressIndicator(strokeWidth: 2),
const SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Center(
child: SizedBox.square(
key: Key('theater-load-more'),
dimension: 22,
child: CircularProgressIndicator(strokeWidth: 2),
),
),
),
)
else if (dramas.isNotEmpty && !theater.hasMore)
Padding(
key: const Key('paged-list-no-more'),
padding: const EdgeInsets.symmetric(vertical: 16),
child: Text(
l10n.noMoreItems,
textAlign: TextAlign.center,
style: const TextStyle(color: AppColors.textHint, fontSize: 12),
SliverToBoxAdapter(
child: Padding(
key: const Key('paged-list-no-more'),
padding: const EdgeInsets.symmetric(vertical: 16),
child: Text(
l10n.noMoreItems,
textAlign: TextAlign.center,
style: const TextStyle(color: AppColors.textHint, fontSize: 12),
),
),
),
];
@@ -401,6 +417,8 @@ class _DiscoverPageState extends State<DiscoverPage> {
Navigator.of(context).push<void>(MaterialPageRoute(builder: (_) => page));
}
Widget _sliverBlock(Widget child) => SliverToBoxAdapter(child: _block(child));
Widget _block(Widget child) => ConstrainedBox(
constraints: const BoxConstraints(minHeight: 280),
child: Center(child: child),