From d1abfd47c7d81f9ef3a7d23fddf9b0ff4df98774 Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Tue, 20 Oct 2026 15:17:46 +0800 Subject: [PATCH] =?UTF-8?q?fix(flutter):=20=E4=BF=AE=E5=A4=8D=E5=89=A7?= =?UTF-8?q?=E5=9C=BA=E5=90=B8=E9=A1=B6=E5=88=86=E7=B1=BB=E8=A1=8C=E9=97=AA?= =?UTF-8?q?=E7=83=81=E5=B9=B6=E5=8E=8B=E7=BC=A9=E9=97=B4=E8=B7=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/discover/discover_view.dart | 57 +++++++++++--------- test/discover/home_all_dramas_grid_test.dart | 40 ++++++++++++++ 2 files changed, 72 insertions(+), 25 deletions(-) diff --git a/lib/pages/discover/discover_view.dart b/lib/pages/discover/discover_view.dart index e83b882..42de61e 100644 --- a/lib/pages/discover/discover_view.dart +++ b/lib/pages/discover/discover_view.dart @@ -60,7 +60,7 @@ class DiscoverPage extends StatefulWidget { class _DiscoverPageState extends State { late final DiscoverLogic logic; - bool _headerScrolled = false; + final ValueNotifier _headerPinned = ValueNotifier(false); bool get embedded => widget.callbacks.embedded; @@ -73,10 +73,8 @@ class _DiscoverPageState extends State { } 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 { @override void dispose() { logic.scroll.removeListener(_onScroll); + _headerPinned.dispose(); logic.dispose(); super.dispose(); } @@ -184,28 +183,36 @@ class _DiscoverPageState extends State { } 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( + 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 { 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( diff --git a/test/discover/home_all_dramas_grid_test.dart b/test/discover/home_all_dramas_grid_test.dart index 05d7fed..2212541 100644 --- a/test/discover/home_all_dramas_grid_test.dart +++ b/test/discover/home_all_dramas_grid_test.dart @@ -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) =>