-
Notifications
You must be signed in to change notification settings - Fork 28.8k
Add snapAnimationCurve to DraggableScrollableSheet #166134
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
base: master
Are you sure you want to change the base?
Add snapAnimationCurve to DraggableScrollableSheet #166134
Conversation
packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart
Outdated
Show resolved
Hide resolved
packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart
Outdated
Show resolved
Hide resolved
packages/flutter/test/widgets/draggable_scrollable_sheet_test.dart
Outdated
Show resolved
Hide resolved
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.
Thanks for working on this!
@gnprice I wonder if you still have thoughts about this since you commented on #121996 awhile back. Maybe as a first step we should just match the native Android curve by default?
The Material spec doesn't seem to say what the curve should be. I only see this predictive back gif, which happens to show the snap animation too. It's definitely not linear. https://m3.material.io/components/bottom-sheets/guidelines
packages/flutter/test/widgets/draggable_scrollable_sheet_test.dart
Outdated
Show resolved
Hide resolved
Adds an optional parameter snapAnimationCurve to DraggableScrollableSheet that controls the behaviour of the snap animation after the sheet is dragged and then released.
4a95375
to
ed40580
Compare
Thanks for taking the time to look at this @justinmc! I added a test that verifies that the sheet follows the given Let me know if I can do anything else. |
Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change). If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
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 👍
packages/flutter/test/widgets/draggable_scrollable_sheet_test.dart
Outdated
Show resolved
Hide resolved
@@ -305,6 +305,7 @@ class DraggableScrollableSheet extends StatefulWidget { | |||
this.snap = false, | |||
this.snapSizes, | |||
this.snapAnimationDuration, | |||
this.snapAnimationCurve = Curves.linear, |
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 we change this default value to something other than linear? (Maybe in a separate PR)
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.
Up to you guys. I'd probably do it in a separate PR but let me know if you want me to change it here :)
Thanks for the review @justinmc! I've fixed the comment. Let me know if I should do something more to get this merged. |
Let's wait for a secondary review from @gnprice who I think has been OOO. |
Yeah, I still feel like adding this API doesn't solve most of the problem — a much bigger improvement would be if we change the default from @Kal-Elx What curve are you planning to use for this argument in your app or apps? How did you choose that curve? Potentially that curve should be the default. |
Cool, good find. (Down at the bottom of that page.) One thing I notice there is that it seems like there are two different curves involved:
Would both of those be controlled by the curve parameter here? (I'm not sure if they would — I don't have my head around all the interactions involved in DraggableScrollableSheet.) If so, then it seems like the API in this PR wouldn't yet be enough to enable an app to match the Material behavior. |
I just went and experimented with Google Maps on an Android phone, since that's an example of an app that uses a bottom sheet with several snap sizes and that generally seems to have a good Material implementation. Dragging and flinging it between the snap sizes, it's definitely using some sort of ease-out curve (one that starts quickly and ends slowly). I'm also realizing now that the gif we discussed above may not be quite on point: it shows the sheet entering, and exiting, but not moving between two snap sizes where both the start and end size have it still existing. I think the latter interaction is what this code is most about, and I'm not sure this code covers either the entrance or exit animations. |
Here's a screen recording of the Google Maps behavior: |
More precisely, as I look more closely: if you drag the sheet to an intermediate point but then let go without flinging (i.e. with your finger not moving, or at least not moving quickly), then it (a) starts from a standstill, just like it was when you let go, but (b) accelerates quickly early on, and then (c) slows down more gradually to a stop. Roughly like Curves.ease. There's an example of this around 00:13 in my screen recording above. I think the behavior here can't be captured by a single curve (or a single
The |
final double averageVelocity = | ||
_pixelSnapSize < position | ||
? math.min(-minimumSpeed, initialVelocity) | ||
: math.max(minimumSpeed, initialVelocity); |
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.
The value of initialVelocity
comes from the user's fling, right? I.e. it's the velocity their finger was moving at when they let go.
With this formula, the actual initial velocity that the snapping simulation moves at will be different from initialVelocity
. For example, if the curve is one that starts quickly and ends slowly, then the sheet will suddenly speed up at the instant the user lets go, and then gradually slow down.
That seems likely to feel unnatural. The existing code already does that if you start from a stop, or from a low speed, thanks to the minimumSpeed
logic here… and I think that's actually one of the major reasons that the current behavior doesn't feel right. See my observations above (#166134 (comment)) about how Google Maps behaves: if you start from low speed then it does accelerate, but smoothly rather than all in one frame.
Instead I think the ideal behavior here would have the simulation's initial velocity, i.e. dx(0.0)
, be equal to initialVelocity
. Then it can speed up and/or slow down smoothly from there.
Hey @gnprice — sorry for the late response and thanks for taking the time to compare snapping behaviors! In the app I’m working on right now we ended up using You’re absolutely right that inserting a curve means the simulation’s actual initial velocity no longer equals the user’s fling velocity but that mismatch already exists in the current api if you set I definitely agree this isn’t the ideal long-term solution, and I’d love to see an implementation that truly matches the Material behavior when there's time to implement it. In the meantime, merging this feels like a practical win for developers without introducing new risk. Of course the final call is yours and the framework team’s. Thanks again for the thoughtful review! 🙏 |
This pull request adds an optional parameter
snapAnimationCurve
toDraggableScrollableSheet
which controls the curve of the snapping animation when the sheet is released after drag.This fixes #121996 where the current snap animation of
DraggableScrollableSheet
is described as linear/mechanic.The reason why it feels mechanic is because the animation is calculated
position + velocity * time
.This pull request changes this calculation to instead calculate the position as
position + displacement * snapAnimationCurve!.transform(progress)
.Here's an example of the result with
snapAnimationCurve: Curves.easeOutQuad
:https://github.com/user-attachments/assets/b54a5821-f2cf-4280-b30d-8138082da3c8
And another with
Simulator.Screen.Recording.-.iPhone.16.-.2025-03-28.at.14.34.39.mp4
Pre-launch Checklist
///
).Discussion
The provided solution does not fully support curves that can take values
> 1
or< 0
, e.g. Curves.easeOutBack. Using one of these curves could try to move the sheet outside of itsminChildSize
andmaxChildSize
which the current implementation does not support so the sheet would just halt at its min and max positions. I think this should be addressed but I would argue that it's outside of the scope of this PR since it's also an existing problem when usinganimateTo
onDraggableScrollableController
and any solution in this area would likely solve both problems.