Correct max height calculation to fade and animate insets on scroll in CupertinoSearchTextField (#166569)
Fixes [CupertinoSliverNavigationBar.search does not fade or animate
insets if search view scrolled in automatic
mode](https://github.com/flutter/flutter/issues/165152)
## Pre-launch Checklist
- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
If you need help, consider asking for advice on the #hackers-new channel
on [Discord].
<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
diff --git a/packages/flutter/lib/src/cupertino/search_field.dart b/packages/flutter/lib/src/cupertino/search_field.dart
index aba2788..d13524d 100644
--- a/packages/flutter/lib/src/cupertino/search_field.dart
+++ b/packages/flutter/lib/src/cupertino/search_field.dart
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:math' as math;
+
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
@@ -363,8 +365,8 @@
TextEditingController get _effectiveController => widget.controller ?? _controller!.value;
ScrollNotificationObserverState? _scrollNotificationObserver;
+ late double _scaledIconSize;
double _fadeExtent = 0.0;
- double? _maxHeight;
@override
void initState() {
@@ -441,12 +443,13 @@
}
void _handleScrollNotification(ScrollNotification notification) {
- if (_maxHeight == null) {
- _maxHeight ??= context.size?.height;
- } else if (notification is ScrollUpdateNotification) {
+ if (notification is ScrollUpdateNotification) {
final double currentHeight = context.size?.height ?? 0.0;
setState(() {
- _fadeExtent = _calculateScrollOpacity(currentHeight, _maxHeight!);
+ _fadeExtent = _calculateScrollOpacity(
+ currentHeight,
+ _scaledIconSize + math.max(widget.prefixInsets.vertical, widget.suffixInsets.vertical),
+ );
});
}
}
@@ -487,7 +490,7 @@
// The icon size will be scaled by a factor of the accessibility text scale,
// to follow the behavior of `UISearchTextField`.
- final double scaledIconSize = MediaQuery.textScalerOf(context).scale(widget.itemSize);
+ _scaledIconSize = MediaQuery.textScalerOf(context).scale(widget.itemSize);
// If decoration was not provided, create a decoration with the provided
// background color and border radius.
@@ -500,7 +503,7 @@
final IconThemeData iconThemeData = IconThemeData(
color: CupertinoDynamicColor.resolve(widget.itemColor, context),
- size: scaledIconSize,
+ size: _scaledIconSize,
);
final Widget prefix = Opacity(
diff --git a/packages/flutter/test/cupertino/search_field_test.dart b/packages/flutter/test/cupertino/search_field_test.dart
index f209d3c..03666fd 100644
--- a/packages/flutter/test/cupertino/search_field_test.dart
+++ b/packages/flutter/test/cupertino/search_field_test.dart
@@ -749,4 +749,67 @@
lessThan(initialPadding),
);
});
+
+ testWidgets('Fades and animates insets on scroll if search field starts out collapsed', (
+ WidgetTester tester,
+ ) async {
+ const TextDirection direction = TextDirection.ltr;
+ const double scrollOffset = 200;
+ await tester.pumpWidget(
+ const Directionality(
+ textDirection: direction,
+ child: CupertinoApp(
+ home: CupertinoPageScaffold(
+ child: CustomScrollView(
+ slivers: <Widget>[
+ CupertinoSliverNavigationBar.search(
+ largeTitle: Text('Large title'),
+ searchField: CupertinoSearchTextField(),
+ ),
+ SliverToBoxAdapter(child: SizedBox(height: 1000)),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+
+ final Finder searchTextFieldFinder = find.byType(CupertinoSearchTextField);
+ expect(searchTextFieldFinder, findsOneWidget);
+
+ final double searchTextFieldHeight = tester.getSize(searchTextFieldFinder).height;
+ await tester.tap(find.widgetWithText(CupertinoSearchTextField, 'Search'), warnIfMissed: false);
+
+ final TestGesture scrollGesture1 = await tester.startGesture(
+ tester.getCenter(find.byType(CustomScrollView)),
+ );
+ await scrollGesture1.moveBy(const Offset(0, -scrollOffset));
+ await scrollGesture1.up();
+ await tester.pumpAndSettle();
+
+ expect(find.text('Cancel'), findsOneWidget);
+ await tester.tap(find.text('Cancel'));
+ await tester.pumpAndSettle();
+
+ final TestGesture scrollGesture2 = await tester.startGesture(
+ tester.getCenter(find.byType(CustomScrollView)),
+ );
+ await scrollGesture2.moveBy(Offset(0, scrollOffset - searchTextFieldHeight / 2));
+ await scrollGesture2.up();
+ await tester.pump();
+
+ final Finder prefixIconFinder = find.descendant(
+ of: searchTextFieldFinder,
+ matching: find.byIcon(CupertinoIcons.search),
+ );
+
+ // The prefix icon has faded.
+ expect(prefixIconFinder, findsOneWidget);
+ expect(
+ tester
+ .widget<Opacity>(find.ancestor(of: prefixIconFinder, matching: find.byType(Opacity)))
+ .opacity,
+ lessThan(1.0),
+ );
+ });
}