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

@@ -10,6 +10,8 @@ import '../../../l10n/app_localizations.dart';
import 'drama_grid.dart';
/// 剧场搜索结果:加载、失败与空结果都留在同一页内。
///
/// 自身是 sliver,好让结果网格跟着剧场页一起懒加载。
class DiscoverSearchResults extends StatelessWidget {
const DiscoverSearchResults({
super.key,
@@ -26,16 +28,24 @@ class DiscoverSearchResults extends StatelessWidget {
final VoidCallback onRetry;
final ValueChanged<Drama> onOpen;
static const EdgeInsets _topPadding = EdgeInsets.only(top: 12);
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Padding(
padding: const EdgeInsets.only(top: 12),
child: _content(l10n),
if (!loading && error == null && results.isNotEmpty) {
return SliverPadding(
padding: _topPadding,
sliver: SliverDramaGrid(l10n: l10n, dramas: results, onOpen: onOpen),
);
}
return SliverPadding(
padding: _topPadding,
sliver: SliverToBoxAdapter(child: _state(l10n)),
);
}
Widget _content(AppLocalizations l10n) {
Widget _state(AppLocalizations l10n) {
if (loading) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 80),
@@ -61,14 +71,11 @@ class DiscoverSearchResults extends StatelessWidget {
onAction: onRetry,
);
}
if (results.isEmpty) {
return EmptyState(
key: const Key('discover-search-empty'),
icon: Icons.search_off_rounded,
title: l10n.noSearchResults,
subtitle: l10n.emptyHint,
);
}
return DramaGrid(l10n: l10n, dramas: results, onOpen: onOpen);
return EmptyState(
key: const Key('discover-search-empty'),
icon: Icons.search_off_rounded,
title: l10n.noSearchResults,
subtitle: l10n.emptyHint,
);
}
}
+34 -25
View File
@@ -10,7 +10,6 @@ import '../../../core/theme/app_dimens.dart';
import '../../../core/widgets/remote_image.dart';
import '../../../l10n/app_localizations.dart';
const int _columns = 2;
const double _columnGap = 8;
const double _cardGap = 14;
const double _gridHorizontalPadding = 12;
@@ -29,8 +28,10 @@ const Color _tagChipBackground = AppColors.raised;
final RegExp _tagSplitter = RegExp(r'[,/、·]');
/// 剧场/分类页共用的双列剧目网格。
class DramaGrid extends StatelessWidget {
const DramaGrid({
///
/// 按行懒加载:翻到第 N 页时只构建当前可见的几张卡,长列表不会把整本目录都留在树上。
class SliverDramaGrid extends StatelessWidget {
const SliverDramaGrid({
super.key,
required this.l10n,
required this.dramas,
@@ -43,33 +44,41 @@ class DramaGrid extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
key: const Key('home-all-dramas-section'),
final rowCount = (dramas.length + 1) ~/ 2;
return SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: _gridHorizontalPadding),
child: Row(
key: const Key('home-all-dramas-grid'),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: _column(0)),
const SizedBox(width: _columnGap),
Expanded(child: _column(1)),
],
sliver: SliverList(
delegate: SliverChildBuilderDelegate((context, row) {
final left = dramas[row * 2];
final rightIndex = row * 2 + 1;
final right = rightIndex < dramas.length ? dramas[rightIndex] : null;
return Padding(
// 行间距沿用卡片间距,首行不需要。
padding: EdgeInsets.only(top: row == 0 ? 0 : _cardGap),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: _card(left)),
const SizedBox(width: _columnGap),
Expanded(
child: right == null
? const SizedBox.shrink()
: _card(right),
),
],
),
);
}, childCount: rowCount),
),
);
}
Widget _column(int start) {
final children = <Widget>[];
for (var index = start; index < dramas.length; index += _columns) {
final drama = dramas[index];
children.add(
DramaGridCard(l10n: l10n, drama: drama, onTap: () => onOpen(drama)),
);
if (index + _columns < dramas.length) {
children.add(const SizedBox(height: _cardGap));
}
}
return Column(children: children);
Widget _card(Drama drama) {
return DramaGridCard(
l10n: l10n,
drama: drama,
onTap: () => onOpen(drama),
);
}
}