fix(flutter): 修复剧场吸顶分类行闪烁并压缩间距

This commit is contained in:
Zhou Weidong committed 2026-10-20 15:17:46 +08:00
1 parent 963f376159
commit d1abfd47c7
2 files changed
+72 -25

No files matched your search

+32 -25
View File
@@ -60,7 +60,7 @@ class DiscoverPage extends StatefulWidget {
class _DiscoverPageState extends State<DiscoverPage> {
late final DiscoverLogic logic;
bool _headerScrolled = false;
final ValueNotifier<bool> _headerPinned = ValueNotifier<bool>(false);
bool get embedded => widget.callbacks.embedded;
@@ -73,10 +73,8 @@ class _DiscoverPageState extends State<DiscoverPage> {
}
void _onScroll() {
final scrolled = logic.scroll.hasClients && logic.scroll.offset > 0;
if (scrolled != _headerScrolled) {
setState(() => _headerScrolled = scrolled);
}
final pinned = logic.scroll.hasClients && logic.scroll.offset > 0;
if (pinned != _headerPinned.value) _headerPinned.value = pinned;
}
void _focusOnFirstFrame() {
@@ -99,6 +97,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
@override
void dispose() {
logic.scroll.removeListener(_onScroll);
_headerPinned.dispose();
logic.dispose();
super.dispose();
}
@@ -184,28 +183,36 @@ class _DiscoverPageState extends State<DiscoverPage> {
}
Widget _stickyHeader(AppLocalizations l10n, CategoryController categories) {
return AnimatedContainer(
key: const Key('theater-sticky-header'),
duration: _stickyHeaderTransition,
curve: Curves.easeOut,
decoration: BoxDecoration(
color: _headerScrolled ? AppColors.ink : Colors.transparent,
border: Border(
bottom: BorderSide(
color: _headerScrolled ? AppColors.border : Colors.transparent,
// 只有背景随吸顶状态变化,分类行内容(child)不参与重建,避免上滑闪烁。
return ValueListenableBuilder<bool>(
valueListenable: _headerPinned,
builder: (context, pinned, child) => AnimatedContainer(
key: const Key('theater-sticky-header'),
duration: _stickyHeaderTransition,
curve: Curves.easeOut,
decoration: BoxDecoration(
color: pinned ? AppColors.ink : Colors.transparent,
border: Border(
bottom: BorderSide(
color: pinned ? AppColors.border : Colors.transparent,
),
),
boxShadow: pinned
? const [
BoxShadow(
color: Color(0x14000000),
blurRadius: 8,
offset: Offset(0, 2),
),
]
: const [],
),
boxShadow: _headerScrolled
? const [
BoxShadow(
color: Color(0x14000000),
blurRadius: 8,
offset: Offset(0, 2),
),
]
: const [],
child: child,
),
child: KeyedSubtree(
key: const Key('theater-sticky-content'),
child: _categoryTabs(l10n, categories),
),
child: _categoryTabs(l10n, categories),
);
}
@@ -341,7 +348,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
Widget _toolbar(AppLocalizations l10n, CategoryController categories) {
return Padding(
padding: const EdgeInsets.fromLTRB(14, 10, 14, 12),
padding: const EdgeInsets.fromLTRB(14, 2, 14, 12),
child: Row(
children: [
Expanded(
@@ -226,6 +226,46 @@ void main() {
expect(header().color, AppColors.ink);
});
testWidgets('should_keep_category_row_stable_across_scrolls', (tester) async {
await _pumpTheater(
tester,
categoryRepository: _GridCategoryRepository(
discoverFeed: _tallDiscoverFeed,
),
);
double categoryTop() =>
tester.getRect(find.byKey(const Key('theater-category-chips'))).top;
final initial = categoryTop();
await tester.drag(_verticalFeed, const Offset(0, -120));
await tester.pumpAndSettle();
final once = categoryTop();
await tester.drag(_verticalFeed, const Offset(0, -120));
await tester.pumpAndSettle();
final twice = categoryTop();
await tester.drag(_verticalFeed, const Offset(0, 400));
await tester.pumpAndSettle();
final backToTop = categoryTop();
expect(once, closeTo(initial, 0.5));
expect(twice, closeTo(initial, 0.5));
expect(backToTop, closeTo(initial, 0.5));
});
testWidgets('should_keep_small_gap_between_category_and_toolbar', (
tester,
) async {
await _pumpTheater(tester, categoryRepository: _GridCategoryRepository());
final categoryBottom = tester
.getRect(find.byKey(const Key('theater-category-chips')))
.bottom;
final toolbarTop = tester
.getRect(find.byKey(const Key('theater-toolbar-filter')))
.top;
expect(toolbarTop - categoryBottom, inInclusiveRange(0, 6));
});
testWidgets('should_show_category_underline_for_selection', (tester) async {
await _pumpTheater(tester, categoryRepository: _GridCategoryRepository());
Color underline(String id) =>