-
Notifications
You must be signed in to change notification settings - Fork 29.7k
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 [email protected] Roll Flutter Engine from 74d40c160e48 to 4ee09d3b7f3b (1 revision) (flutter/flutter#151346) 2024-07-05 [email protected] Roll Flutter Engine from ba9c7b6336ef to 74d40c160e48 (1 revision) (flutter/flutter#151340) 2024-07-05 [email protected] Roll Flutter Engine from 1f0f950ea02a to ba9c7b6336ef (1 revision) (flutter/flutter#151331) 2024-07-05 [email protected] Roll Flutter Engine from 3c6a373bda3e to 1f0f950ea02a (1 revision) (flutter/flutter#151326) 2024-07-04 [email protected] Roll Flutter Engine from 79a91e38c587 to 3c6a373bda3e (2 revisions) (flutter/flutter#151318) 2024-07-04 [email protected] Roll Packages from d2705fb to 754de19 (3 revisions) (flutter/flutter#151315) 2024-07-04 [email protected] Roll Flutter Engine from 2b6bb516e7e6 to 79a91e38c587 (2 revisions) (flutter/flutter#151314) 2024-07-04 [email protected] Roll Flutter Engine from 8e2d05fa95d7 to 2b6bb516e7e6 (2 revisions) (flutter/flutter#151299) 2024-07-04 [email protected] Roll Flutter Engine from 4190543cb093 to 8e2d05fa95d7 (13 revisions) (flutter/flutter#151293) 2024-07-03 [email protected] Roll pub packages (flutter/flutter#151203) 2024-07-03 [email protected] Fix invalid URL suggestion for gradle incompatability (flutter/flutter#150999) 2024-07-03 [email protected] Cupertino transparent navigation bars (flutter/flutter#149102) 2024-07-03 [email protected] Prepares semantics_update_test for upcoming link URL change (flutter/flutter#151261) 2024-07-03 [email protected] Add a message about spam/brigading (flutter/flutter#150583) 2024-07-03 [email protected] PinnedHeaderSliver example based on the iOS Settings AppBar (flutter/flutter#151205) 2024-07-03 [email protected] Update deprecation policy (flutter/flutter#151257) 2024-07-03 [email protected] SliverFloatingHeader (flutter/flutter#151145) 2024-07-03 [email protected] Remove warning when KGP version not detected (flutter/flutter#151254) 2024-07-03 [email protected] Feat: Add withOpacity to gradient (flutter/flutter#150670) 2024-07-03 [email protected] Fix references in examples (flutter/flutter#151204) 2024-07-03 [email protected] Fix link in tree hygene doc (flutter/flutter#151235) 2024-07-03 [email protected] content dimensions are not established get controller value error (flutter/flutter#148938) 2024-07-03 [email protected] chore: fix typos and link broken (flutter/flutter#150402) 2024-07-03 [email protected] Add example of goldenFileComparator usage in widget tests (flutter/flutter#150422) 2024-07-03 [email protected] Fix project name fallback (flutter/flutter#150614) 2024-07-03 [email protected] Roll Flutter Engine from a3e61c0fd1c2 to 4190543cb093 (1 revision) (flutter/flutter#151241) 2024-07-03 [email protected] Roll Flutter Engine from 8274f54f11be to a3e61c0fd1c2 (2 revisions) (flutter/flutter#151237) 2024-07-03 [email protected] Force regeneration of platform-specific manifests before running performance tests (flutter/flutter#151003) 2024-07-03 [email protected] Handle a SocketException thrown when sending the browser close command to Chrome (flutter/flutter#151197) 2024-07-03 [email protected] Roll Flutter Engine from a02e3f673da3 to 8274f54f11be (4 revisions) (flutter/flutter#151226) 2024-07-03 [email protected] Roll Flutter Engine from c5c0c54d6d1d to a02e3f673da3 (1 revision) (flutter/flutter#151212) 2024-07-03 [email protected] Roll Flutter Engine from 44278941443e to c5c0c54d6d1d (9 revisions) (flutter/flutter#151208) 2024-07-02 [email protected] Fix scheduler event loop being stuck due to task with Priority.idle (flutter/flutter#151168) 2024-07-02 [email protected] Fix result propagation in RenderSliverEdgeInsetsPadding.hitTestChildren (flutter/flutter#149825) 2024-07-02 [email protected] docImports for flutter_test (flutter/flutter#151189) 2024-07-02 [email protected] Interactable ScrollView content when settling a scroll activity (flutter/flutter#145848) 2024-07-02 [email protected] [flutter_tools] Update the mapping for the Dart SDK internal URI (flutter/flutter#151170) 2024-07-02 [email protected] Roll pub packages (flutter/flutter#151129) 2024-07-02 [email protected] Fix typo (flutter/flutter#151192) 2024-07-02 [email protected] [tool] Fix `stdin.flush` calls on processes started by `FakeProcessManager` (flutter/flutter#151183) 2024-07-02 [email protected] 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 [email protected],[email protected],[email protected] 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