-
Notifications
You must be signed in to change notification settings - Fork 29.8k
PinnedHeaderSliver example based on the iOS Settings AppBar #151205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
782d25e to
07a03e5
Compare
|
Chiming in with a turbonit so free to ignore. One potential problem is the header changing size without scrolling. e.g. class _SettingsAppBarExampleState extends State<SettingsAppBarExample> {
final GlobalKey headerSliverKey = GlobalKey();
final GlobalKey titleSliverKey = GlobalKey();
late final ScrollController scrollController;
double headerOpacity = 0;
double _titleHeight = 50;
@override
void initState() {
super.initState();
scrollController = ScrollController();
}
@override
void dispose() {
scrollController.dispose();
super.dispose();
}
// The key must be for a widget _below_ a RenderSliver so that
// findAncestorRenderObjectOfType can find the RenderSliver when it searches
// the key widget's renderer ancesotrs.
RenderSliver? keyToSliver(GlobalKey key) =>
key.currentContext?.findAncestorRenderObjectOfType<RenderSliver>();
// Each time the app's list scrolls: if the Title sliver has scrolled completely behind
// the (pinned) header sliver, then change the header's opacity from 0 to 1.
//
// The header RenderSliver's SliverConstraints.scrollOffset is the distance
// above the top of the viewport where the top of header sliver would appear
// if it were laid out normally. Since it's a pinned sliver, it's unconditionally
// painted at the top of the viewport, even though its scrollOffset constraint
// increases as the user scrolls upwards. The "Settings" title RenderSliver's
// scrollExtent is the vertical space it wants to occupy. It doesn't change as
// the user scrolls.
bool handleScrollNotification(ScrollNotification notification) {
final RenderSliver? headerSliver = keyToSliver(headerSliverKey);
final RenderSliver? titleSliver = keyToSliver(titleSliverKey);
if (headerSliver != null && titleSliver != null && titleSliver.geometry != null) {
final double opacity = headerSliver.constraints.scrollOffset > titleSliver.geometry!.scrollExtent ? 1 : 0;
if (opacity != headerOpacity) {
setState(() {
headerOpacity = opacity;
});
}
}
return false;
}
@override
Widget build(BuildContext context) {
const EdgeInsets horizontalPadding = EdgeInsets.symmetric(horizontal: 8);
final ThemeData theme = Theme.of(context);
final TextTheme textTheme = theme.textTheme;
final ColorScheme colorScheme = theme.colorScheme;
return Scaffold(
backgroundColor: colorScheme.surfaceContainer,
body: SafeArea(
child: NotificationListener<ScrollNotification>(
onNotification: handleScrollNotification,
child: CustomScrollView(
controller: scrollController,
slivers: <Widget>[
PinnedHeaderSliver(
child: Header(
key: headerSliverKey,
opacity: headerOpacity,
child: Text('Settings', style: textTheme.titleMedium),
),
),
SliverPadding(
padding: horizontalPadding,
sliver: SliverToBoxAdapter(
child: SizedBox(
height: _titleHeight,
child: TitleItem(
child: GestureDetector(
onTap: () {
setState(() {
_titleHeight = Random().nextInt(200) + 50.0;
});
},
child: TitleItem(
key: titleSliverKey,
child: Text(
'Settings',
style: textTheme.titleLarge!.copyWith(
fontWeight: FontWeight.bold,
fontSize: 32,
),
),
),
),
),
),
),
),
const SliverPadding(
padding: horizontalPadding,
sliver: ItemList(),
),
],
),
),
),
);
}
}(in the video, I click on the title to trigger a size change) Simulator.Screen.Recording.-.iPhone.15.Pro.-.2024-07-03.at.10.40.25.mp4At some point I think we handled it like this, but our scroll management doesn't account for vertical padding being added to the title sliver (also, the code doesn't look as elegant) class _SettingsAppBarExampleState extends State<SettingsAppBarExample> {
final GlobalKey _titleKey = GlobalKey();
late final ScrollController _scrollController;
double headerOpacity = 0;
double _dynamicTitleHeight = 50;
double? get _titleLayoutHeight => _titleKey.currentContext?.size?.height;
@override
void initState() {
super.initState();
_scrollController = ScrollController()..addListener(_handleScroll);
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void _handleScroll() {
if (!_scrollController.hasClients || _titleLayoutHeight == null) {
return;
}
final double opacity = _titleLayoutHeight! < _scrollController.offset ? 1 : 0;
if (opacity != headerOpacity) {
setState(() {
headerOpacity = opacity;
});
}
}
bool _handleTitleSizeChange(SizeChangedLayoutNotification notification) {
SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
_handleScroll();
});
return false;
}
@override
Widget build(BuildContext context) {
const EdgeInsets horizontalPadding = EdgeInsets.symmetric(horizontal: 8);
final ThemeData theme = Theme.of(context);
final TextTheme textTheme = theme.textTheme;
final ColorScheme colorScheme = theme.colorScheme;
return Scaffold(
backgroundColor: colorScheme.surfaceContainer,
body: SafeArea(
child: CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
PinnedHeaderSliver(
child: Header(
opacity: headerOpacity,
child: Text('Settings', style: textTheme.titleMedium),
),
),
SliverPadding(
padding: horizontalPadding,
sliver: SliverToBoxAdapter(
child: NotificationListener<SizeChangedLayoutNotification>(
onNotification: _handleTitleSizeChange,
child: SizeChangedLayoutNotifier(
child: SizedBox(
height: _dynamicTitleHeight,
child: TitleItem(
key: _titleKey,
child: GestureDetector(
onTap: () {
setState(() {
_dynamicTitleHeight = Random().nextInt(200) + 50.0;
});
},
child: Text(
'Settings',
style: textTheme.titleLarge!.copyWith(
fontWeight: FontWeight.bold,
fontSize: 32,
),
),
),
),
),
),
),
),
),
const SliverPadding(
padding: horizontalPadding,
sliver: ItemList(),
),
],
),
),
);
}
}Simulator.Screen.Recording.-.iPhone.15.Pro.-.2024-07-03.at.10.38.50.mp4Also, the example I wrote occasionally prevents me from scrolling when I refresh the simulator, but I'm not sure if that's the code or if the XCode deities are angry today. |
1fb8923 to
2593c83
Compare
MitchellGoodwin
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Can't find anything to nitpick.
| /// | ||
| /// {@tool dartpad} | ||
| /// A more elaborate example which creates an app bar that's similar to the one | ||
| /// that appears in the iOS Settings app. In this example the pinned header |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should probably point to CupertinoSliverNavigationBar as our out of the box solution once #149102 is merged. We could point to it now, but the Cupertino nav bars don't automatically go transparent yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We also need an easy way to incorporate a sliver that behaves like the iOS settings search textfield: doesn't appear until the user scrolls downwards, resizes to zero when the user scrolls upwards, absorbs the upwards scroll until its extent is zero.
|
@davidhicks980 - deciding how resizing the title item should affect the layout while a scroll is underway is a challenging complication. I haven't tried to address it here; thanks for the example and explanation! In the interest of keeping things simple, I'm going to leave this example as it is. There's clearly more work to be done to make scroll-driven layouts and animations easier to define. |
|
auto label is removed for flutter/flutter/151205, due to - The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label. |
Roll Flutter from af913a7 to fafd67d (41 revisions) flutter/flutter@af913a7...fafd67d 2024-07-05 engine-flutter-autoroll@skia.org Roll Flutter Engine from 74d40c160e48 to 4ee09d3b7f3b (1 revision) (flutter/flutter#151346) 2024-07-05 engine-flutter-autoroll@skia.org Roll Flutter Engine from ba9c7b6336ef to 74d40c160e48 (1 revision) (flutter/flutter#151340) 2024-07-05 engine-flutter-autoroll@skia.org Roll Flutter Engine from 1f0f950ea02a to ba9c7b6336ef (1 revision) (flutter/flutter#151331) 2024-07-05 engine-flutter-autoroll@skia.org Roll Flutter Engine from 3c6a373bda3e to 1f0f950ea02a (1 revision) (flutter/flutter#151326) 2024-07-04 engine-flutter-autoroll@skia.org Roll Flutter Engine from 79a91e38c587 to 3c6a373bda3e (2 revisions) (flutter/flutter#151318) 2024-07-04 engine-flutter-autoroll@skia.org Roll Packages from d2705fb to 754de19 (3 revisions) (flutter/flutter#151315) 2024-07-04 engine-flutter-autoroll@skia.org Roll Flutter Engine from 2b6bb516e7e6 to 79a91e38c587 (2 revisions) (flutter/flutter#151314) 2024-07-04 engine-flutter-autoroll@skia.org Roll Flutter Engine from 8e2d05fa95d7 to 2b6bb516e7e6 (2 revisions) (flutter/flutter#151299) 2024-07-04 engine-flutter-autoroll@skia.org Roll Flutter Engine from 4190543cb093 to 8e2d05fa95d7 (13 revisions) (flutter/flutter#151293) 2024-07-03 137456488+flutter-pub-roller-bot@users.noreply.github.com Roll pub packages (flutter/flutter#151203) 2024-07-03 Dispersia@users.noreply.github.com Fix invalid URL suggestion for gradle incompatability (flutter/flutter#150999) 2024-07-03 423393+veloce@users.noreply.github.com Cupertino transparent navigation bars (flutter/flutter#149102) 2024-07-03 1961493+harryterkelsen@users.noreply.github.com Prepares semantics_update_test for upcoming link URL change (flutter/flutter#151261) 2024-07-03 nate.w5687@gmail.com Add a message about spam/brigading (flutter/flutter#150583) 2024-07-03 hans.muller@gmail.com PinnedHeaderSliver example based on the iOS Settings AppBar (flutter/flutter#151205) 2024-07-03 katelovett@google.com Update deprecation policy (flutter/flutter#151257) 2024-07-03 hans.muller@gmail.com SliverFloatingHeader (flutter/flutter#151145) 2024-07-03 34871572+gmackall@users.noreply.github.com Remove warning when KGP version not detected (flutter/flutter#151254) 2024-07-03 34465683+rkishan516@users.noreply.github.com Feat: Add withOpacity to gradient (flutter/flutter#150670) 2024-07-03 goderbauer@google.com Fix references in examples (flutter/flutter#151204) 2024-07-03 82763757+NobodyForNothing@users.noreply.github.com Fix link in tree hygene doc (flutter/flutter#151235) 2024-07-03 yinxulolol@gmail.com content dimensions are not established get controller value error (flutter/flutter#148938) 2024-07-03 michaleli@foxmail.com chore: fix typos and link broken (flutter/flutter#150402) 2024-07-03 dev@alestiago.com Add example of goldenFileComparator usage in widget tests (flutter/flutter#150422) 2024-07-03 brackenavaron@gmail.com Fix project name fallback (flutter/flutter#150614) 2024-07-03 engine-flutter-autoroll@skia.org Roll Flutter Engine from a3e61c0fd1c2 to 4190543cb093 (1 revision) (flutter/flutter#151241) 2024-07-03 engine-flutter-autoroll@skia.org Roll Flutter Engine from 8274f54f11be to a3e61c0fd1c2 (2 revisions) (flutter/flutter#151237) 2024-07-03 jason-simmons@users.noreply.github.com Force regeneration of platform-specific manifests before running performance tests (flutter/flutter#151003) 2024-07-03 jason-simmons@users.noreply.github.com Handle a SocketException thrown when sending the browser close command to Chrome (flutter/flutter#151197) 2024-07-03 engine-flutter-autoroll@skia.org Roll Flutter Engine from a02e3f673da3 to 8274f54f11be (4 revisions) (flutter/flutter#151226) 2024-07-03 engine-flutter-autoroll@skia.org Roll Flutter Engine from c5c0c54d6d1d to a02e3f673da3 (1 revision) (flutter/flutter#151212) 2024-07-03 engine-flutter-autoroll@skia.org Roll Flutter Engine from 44278941443e to c5c0c54d6d1d (9 revisions) (flutter/flutter#151208) 2024-07-02 jon@jon.sg Fix scheduler event loop being stuck due to task with Priority.idle (flutter/flutter#151168) 2024-07-02 matej.knopp@gmail.com Fix result propagation in RenderSliverEdgeInsetsPadding.hitTestChildren (flutter/flutter#149825) 2024-07-02 goderbauer@google.com docImports for flutter_test (flutter/flutter#151189) 2024-07-02 Michal-MK@users.noreply.github.com Interactable ScrollView content when settling a scroll activity (flutter/flutter#145848) 2024-07-02 danny@tuppeny.com [flutter_tools] Update the mapping for the Dart SDK internal URI (flutter/flutter#151170) 2024-07-02 137456488+flutter-pub-roller-bot@users.noreply.github.com Roll pub packages (flutter/flutter#151129) 2024-07-02 36861262+QuncCccccc@users.noreply.github.com Fix typo (flutter/flutter#151192) 2024-07-02 andrewrkolos@gmail.com [tool] Fix `stdin.flush` calls on processes started by `FakeProcessManager` (flutter/flutter#151183) 2024-07-02 engine-flutter-autoroll@skia.org Roll Flutter Engine from 433d360eee11 to 44278941443e (4 revisions) (flutter/flutter#151186) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC camillesimon@google.com,rmistry@google.com,stuartmorgan@google.com on the revert to ensure that a human ...
…151205) A relatively elaborate PinnedSliverHeader example which creates an app bar that's similar to the one that appears in the iOS Settings app. In this example the pinned header starts out transparent and the first item in the list serves as the app's "Settings" title. When the title item has been scrolled completely behind the pinned header, the header animates its opacity from 0 to 1 and its (centered) "Settings" title appears. The fact that the header's opacity depends on the height of the title item - which is unknown until the list has been laid out - necessitates monitoring its SliverConstraints.scrollExtent from a scroll NotificationListener. https://github.com/flutter/flutter/assets/1377460/539e2591-d0d7-4508-8ce8-4b8f587d7648
A relatively elaborate PinnedSliverHeader example which creates an app bar that's similar to the one that appears in the iOS Settings app. In this example the pinned header starts out transparent and the first item in the list serves as the app's "Settings" title. When the title item has been scrolled completely behind the pinned header, the header animates its opacity from 0 to 1 and its (centered) "Settings" title appears. The fact that the header's opacity depends on the height of the title item - which is unknown until the list has been laid out - necessitates monitoring its SliverConstraints.scrollExtent from a scroll NotificationListener.
Screen.Recording.2024-07-02.at.5.32.25.PM.mov