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

This commit is contained in:
Zhou Weidong committed 2026-10-20 15:17:46 +08:00
commit 72410b2a87
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> { 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,28 +183,36 @@ class _DiscoverPageState extends State<DiscoverPage> {
} }
Widget _stickyHeader(AppLocalizations l10n, CategoryController categories) { Widget _stickyHeader(AppLocalizations l10n, CategoryController categories) {
return AnimatedContainer( // 只有背景随吸顶状态变化,分类行内容(child)不参与重建,避免上滑闪烁。
key: const Key('theater-sticky-header'), return ValueListenableBuilder<bool>(
duration: _stickyHeaderTransition, valueListenable: _headerPinned,
curve: Curves.easeOut, builder: (context, pinned, child) => AnimatedContainer(
decoration: BoxDecoration( key: const Key('theater-sticky-header'),
color: _headerScrolled ? AppColors.ink : Colors.transparent, duration: _stickyHeaderTransition,
border: Border( curve: Curves.easeOut,
bottom: BorderSide( decoration: BoxDecoration(
color: _headerScrolled ? AppColors.border : Colors.transparent, 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 child: child,
? const [ ),
BoxShadow( child: KeyedSubtree(
color: Color(0x14000000), key: const Key('theater-sticky-content'),
blurRadius: 8, child: _categoryTabs(l10n, categories),
offset: Offset(0, 2),
),
]
: const [],
), ),
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) =>