merge: 合并 K34-G 剧场吸顶修复

This commit is contained in:
Zhou Weidong committed 2026-10-20 15:17:46 +08:00
commit 72410b2a87
2 files changed
+57 -10

No files matched your search

+17 -10
View File
@@ -60,7 +60,7 @@ class DiscoverPage extends StatefulWidget {
class _DiscoverPageState extends State<DiscoverPage> { class _DiscoverPageState extends State<DiscoverPage> {
late final DiscoverLogic logic; late final DiscoverLogic logic;
bool _headerScrolled = false; final ValueNotifier<bool> _headerPinned = ValueNotifier<bool>(false);
bool get embedded => widget.callbacks.embedded; bool get embedded => widget.callbacks.embedded;
@@ -73,10 +73,8 @@ class _DiscoverPageState extends State<DiscoverPage> {
} }
void _onScroll() { void _onScroll() {
final scrolled = logic.scroll.hasClients && logic.scroll.offset > 0; final pinned = logic.scroll.hasClients && logic.scroll.offset > 0;
if (scrolled != _headerScrolled) { if (pinned != _headerPinned.value) _headerPinned.value = pinned;
setState(() => _headerScrolled = scrolled);
}
} }
void _focusOnFirstFrame() { void _focusOnFirstFrame() {
@@ -99,6 +97,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
@override @override
void dispose() { void dispose() {
logic.scroll.removeListener(_onScroll); logic.scroll.removeListener(_onScroll);
_headerPinned.dispose();
logic.dispose(); logic.dispose();
super.dispose(); super.dispose();
} }
@@ -184,18 +183,21 @@ class _DiscoverPageState extends State<DiscoverPage> {
} }
Widget _stickyHeader(AppLocalizations l10n, CategoryController categories) { Widget _stickyHeader(AppLocalizations l10n, CategoryController categories) {
return AnimatedContainer( // 只有背景随吸顶状态变化,分类行内容(child)不参与重建,避免上滑闪烁。
return ValueListenableBuilder<bool>(
valueListenable: _headerPinned,
builder: (context, pinned, child) => AnimatedContainer(
key: const Key('theater-sticky-header'), key: const Key('theater-sticky-header'),
duration: _stickyHeaderTransition, duration: _stickyHeaderTransition,
curve: Curves.easeOut, curve: Curves.easeOut,
decoration: BoxDecoration( decoration: BoxDecoration(
color: _headerScrolled ? AppColors.ink : Colors.transparent, color: pinned ? AppColors.ink : Colors.transparent,
border: Border( border: Border(
bottom: BorderSide( bottom: BorderSide(
color: _headerScrolled ? AppColors.border : Colors.transparent, color: pinned ? AppColors.border : Colors.transparent,
), ),
), ),
boxShadow: _headerScrolled boxShadow: pinned
? const [ ? const [
BoxShadow( BoxShadow(
color: Color(0x14000000), color: Color(0x14000000),
@@ -205,7 +207,12 @@ class _DiscoverPageState extends State<DiscoverPage> {
] ]
: const [], : 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) { Widget _toolbar(AppLocalizations l10n, CategoryController categories) {
return Padding( return Padding(
padding: const EdgeInsets.fromLTRB(14, 10, 14, 12), padding: const EdgeInsets.fromLTRB(14, 2, 14, 12),
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
@@ -226,6 +226,46 @@ void main() {
expect(header().color, AppColors.ink); 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 { testWidgets('should_show_category_underline_for_selection', (tester) async {
await _pumpTheater(tester, categoryRepository: _GridCategoryRepository()); await _pumpTheater(tester, categoryRepository: _GridCategoryRepository());
Color underline(String id) => Color underline(String id) =>