CupertinoSheetRoute with scrolling and dragging#177337
CupertinoSheetRoute with scrolling and dragging#177337auto-submit[bot] merged 31 commits intoflutter:masterfrom
Conversation
|
missing one minor bit of fidelity. if you are scrolled down and fling to the top it should spring at the top instead of slam to a halt |
|
The physics when scrolling to the top is definitely not there yet. It either stops suddenly or bounces when it shouldn't when the drag to dismiss happens. I opened this draft PR to get some feedback on the overall approach. |
| return Navigator.of(context, rootNavigator: true).push<T>(route); | ||
| } else { | ||
| widgetBuilder = (BuildContext context) { | ||
| Widget nestedNavigationContent(Widget child) { |
There was a problem hiding this comment.
Since this is changed to define an inline function, is the else necessary?
| context, | ||
| rootNavigator: true, | ||
| ).push<T>(CupertinoSheetRoute<T>(builder: widgetBuilder, enableDrag: enableDrag)); | ||
| final PageRoute<T> route = effectiveBuilder != null |
There was a problem hiding this comment.
Can you simplify the logic to de-dupe and call on your new wrapper method based on useNestedNavigation?
| } | ||
| } | ||
|
|
||
| class _NeverUserScrollableScrollPhysics extends ScrollPhysics { |
There was a problem hiding this comment.
Why is this needed over using NeverScrollableScrollPhysics?
There was a problem hiding this comment.
I didn't want to set allowImplicitScrolling to false.
7eae9f1 to
f165a18
Compare
| child: _CupertinoSheetScope(child: builder(context)), | ||
| child: Builder( | ||
| builder: (BuildContext context) { | ||
| return _CupertinoSheetScope(sheetContext: context, child: _effectiveBuilder(context)); |
There was a problem hiding this comment.
Why do we need to pass around context? That's an anti-pattern.
There was a problem hiding this comment.
Ultimately it's so that _CupertinoDragGestureDetector is able to figure out the height of the sheet for knowing how much to adjust the page transition in response to a user drag.
It gets there through CupertinoSheetDragArea looking up the _CupertinoSheetScope above it and passing along the context to _CupertinoDragGestureDetector.
There was a problem hiding this comment.
Yeah, it's really not something we should do. Passing around build context is not safe. DraggableScrollableSheet keeps track of the sheet's extent. Are we able to do that here?
There was a problem hiding this comment.
I think I can change it so that _CupertinoSheetScope tracks the height/extent rather than the context and that'll work pretty smoothly.
chunhtai
left a comment
There was a problem hiding this comment.
For Accessibility, we would need to focus on two things
- dismissable
in talkback this is fine as user can do gesture down and left to issue a android back.
For voiceOver though, it is two fingers Z gesture and it calls Semantics(onDismiss). Looking at the semantics tree, I didnt see the semantics action is set.
- the draghandle
In talkback, tap should be able to dismiss the sheet similar to
For voicerOver, we may need to check the iOS behavior, do you know whether there is builtin app that uses the native draggable sheet?
| bool get enableDrag; | ||
|
|
||
| /// Determines whether the content can be scrolled. | ||
| bool get enableScroll; |
|
also, while not yet wired up in the engine, can you also apply SemanticsRole.dragHandle to the handle so that we won't forget about this in the future when it is wired up |
You can find the native sheet in the Contacts app, when you add a new contact. You can also find another one in the Settings app through General -> Language & Region -> Add Language |
CupertinoSheet currently does not add a drag handle. For the native sheet widget, the drag handle is normally added when the sheet is resizable, which CupertinoSheet does not do by default, nor is this PR adding that. However, CupertinoSheetDragArea could be wrapped around a drag handle type widget manually added by a dev. So should we either
|
|
I tested on a device both the native behavior and this code. On native the two finger Z-scrub gesture does not dismiss the sheet, it seems. Double tap, then drag does dismiss it however. For CupertinoSheet, currently the Z-scrub gesture does dismiss it, the same with double tap and drag. Should we block the two finger Z gesture? |
@MitchellGoodwin have you checked the native behavior with accessibility controls enabled? Does SwiftUI expose the ability to add drag handles? |
@Piinks I have checked the native behavior with accessibility controls, using a physical device. For the drag handle on native, they do expose a way to add it through prefersGrabberVisibile. They refer to the drag handle as a "grabber". From the HIGs documentation on sheets, they say: "Include a grabber in a resizable sheet. A grabber shows people that they can drag the sheet to resize it; they can also tap it to cycle through the detents. In addition to providing a visual indicator of resizability, a grabber also works with VoiceOver so people can resize the sheet without seeing the screen." I filled an issue for the drag handle #179358. I think that can be done in a separate PR from this one. |
943dd41 to
b0d7ced
Compare
justinmc
left a comment
There was a problem hiding this comment.
Just trying to think through an alternative approach... Rather than a new constructor for CupertinoSheetRoute that passes a ScrollController, could you use PrimaryScrollController.of inside of CupertinoSheetRoute and ask users to do the same and pass that to their relevant scroll view? I'm no expert in this area but it seems kind of aligned with the intention of PrimaryScrollController.
If not PrimaryScrollController, then would a new similar InheritedWidget make sense? ...Or would it just make this more complicated and less discoverable?
Otherwise, do you know how this kind of thing works in SwiftUI? Is it always the entire CupertinoSheet that is scrollable?
Assuming none of these drive-by ideas is viable, and that this covers the main use cases from SwiftUI, then this approach looks good to me!
| /// This is a convenience method for displaying [CupertinoSheetRoute] for common, | ||
| /// straightforward use cases. The Widget returned from `pageBuilder` will be | ||
| /// used to display the content on the [CupertinoSheetRoute]. | ||
| /// straightforward use cases. There are two different build methods that are provided | ||
| /// by the method for building the content of the [CupertinoSheetRoute]. Use `builder` | ||
| /// if there is no content within the sheet that needs to scroll. This is good | ||
| /// for sheets with a simple display, however scrolling gestures will conflict | ||
| /// with the drag to dismiss gesture. `scrollableBuilder` will enable scrollable | ||
| /// content within the sheet. See [CupertinoSheetRoute.scrollable] for more | ||
| /// information. |
There was a problem hiding this comment.
Nit: Should you mention that this is for vertical scrolling?
There was a problem hiding this comment.
Good callout
@justinmc The only issue with that is that we would need to know that the sheet content is intended to be scrollable. So, we could provide the controller with
I don't think a new widget would be needed. As far the user of this API would be concerned, the scroll controller isn't unique in how they implement it. This would make this API further different from DraggableScrollableSheet, so there's a difference in consistency there.
You can just put a scroll view within the sheet and it works as expected. Scrollable areas in Swift know when they are the child of a resizable area and handle things accordingly. Similarly they are able to have priority over the drag gesture that might be on the rest of the sheet. The most common use case is that the majority of the sheet, except for the nav bar is scrollable. So the situation in the example. |
5e792b0 to
5f33ef9
Compare
flutter/flutter@bfc9041...def9ca9 2026-01-25 [email protected] Roll Skia from f1433eb44a50 to 2830fbe8bafe (1 revision) (flutter/flutter#181464) 2026-01-25 [email protected] Roll Fuchsia Linux SDK from 6xoKGIry6Y8T8x5Sa... to T4qTEa3T5CCSCIoJY... (flutter/flutter#181458) 2026-01-24 [email protected] Roll Skia from b6d396a151bc to f1433eb44a50 (1 revision) (flutter/flutter#181449) 2026-01-24 [email protected] Roll Dart SDK from 29918a54dd5c to 60553fc4c04f (1 revision) (flutter/flutter#181437) 2026-01-24 [email protected] Roll Fuchsia Linux SDK from n7NohL9DPpEuPjNt9... to 6xoKGIry6Y8T8x5Sa... (flutter/flutter#181438) 2026-01-24 [email protected] [Impeller] Fix perspective clips with a large perspective bias (flutter/flutter#181434) 2026-01-24 [email protected] Roll Dart SDK from e82d7ad1855e to 29918a54dd5c (4 revisions) (flutter/flutter#181435) 2026-01-24 [email protected] Roll Skia from 32b52343e757 to b6d396a151bc (4 revisions) (flutter/flutter#181431) 2026-01-24 [email protected] [Impeller] Fix interpolation error in Rect::TransformAndClipBounds (flutter/flutter#181420) 2026-01-23 [email protected] Roll Skia from 6d438894c2a8 to 32b52343e757 (2 revisions) (flutter/flutter#181419) 2026-01-23 [email protected] [Material] modernize Typography._withPlatform with Dart 3 switch expression (flutter/flutter#181398) 2026-01-23 [email protected] CupertinoSheetRoute with scrolling and dragging (flutter/flutter#177337) 2026-01-23 [email protected] Adds contents of keys file when a skia gold error occurs. (flutter/flutter#181401) 2026-01-23 [email protected] Roll Skia from e4bd0a355e68 to 6d438894c2a8 (3 revisions) (flutter/flutter#181405) 2026-01-23 [email protected] bump KGP and AGP max known versions (flutter/flutter#181325) 2026-01-23 [email protected] Roll Skia from db10db8bd55f to e4bd0a355e68 (3 revisions) (flutter/flutter#181391) 2026-01-23 [email protected] Roll Packages from 9010299 to 5af5f50 (4 revisions) (flutter/flutter#181388) 2026-01-23 [email protected] Look for project root for FeatureFlags manifest (flutter/flutter#180689) 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] on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Fixes flutter#161687 Enables scrollable content within the sheet to work together with the dragging animation. Flutter: https://github.com/user-attachments/assets/3a9994e3-078a-410e-8288-95592713ab39 Native: https://github.com/user-attachments/assets/c0bd46fb-26f5-41a6-bf42-6c78c23ddf34 Fling when scrolling to the top vs fling when the scrollable content is already at the top https://github.com/user-attachments/assets/e934facc-36d2-4e81-9ccd-a3e9dd7b6201 When scrolling is enabled, then the sheet will no longer have a drag gesture recognizer over the sheet content, and will instead rely on the scrollable content to trigger the drag. A non-scrolling area can be wrapped with `CupertinoSheetDragArea` to put a drag gesture recognizer only on that area, convenient for navbars. See [cupertino_sheet.3.dart](https://github.com/flutter/flutter/pull/177337/files#diff-3df71aa5e6ddb6184007a85e847a9a4883515697b7014764e1978d240d72e0e1) for a full example. ## 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. - [x] 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. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- 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
|
Steps to reproduce Expected results Actual results Note: PR #177337 (merged Jan 23, 2026) was supposed to fix this issue (#161687), but the problem persists on stable 3.41.2. Uploading FILE 2026-02-25 16:14:58.mp4… import 'package:flutter/cupertino.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { @OverRide class HomePage extends StatelessWidget { @OverRide class _MySheetPage extends StatefulWidget { @OverRide class _MySheetPageState extends State<_MySheetPage> { } class _GrabHandle extends StatelessWidget { @OverRide class _CupertinoCard extends StatelessWidget { const _CupertinoCard({required this.title, required this.subtitle}); @OverRide |
Fixes #161687
Enables scrollable content within the sheet to work together with the dragging animation.
Flutter:
Screen.Recording.2025-11-25.at.2.14.02.PM.mov
Native:
Screen.Recording.2025-10-16.at.1.42.10.PM.mov
Fling when scrolling to the top vs fling when the scrollable content is already at the top
Screen.Recording.2025-11-25.at.2.16.13.PM.mov
When scrolling is enabled, then the sheet will no longer have a drag gesture recognizer over the sheet content, and will instead rely on the scrollable content to trigger the drag. A non-scrolling area can be wrapped with
CupertinoSheetDragAreato put a drag gesture recognizer only on that area, convenient for navbars. See cupertino_sheet.3.dart for a full example.Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.