-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Added option to specify you want the keyboard to be dismissed when you scroll. #52068
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
|
@gspencergoog Can you check out what I'm doing here to make sure there isn't a different way we'd want to do this? Basically we want to make it easy for people to specify how a keyboard should be dismissed based on a scrollview's interaction. It's the equivalent to this: https://developer.apple.com/documentation/uikit/uiscrollviewkeyboarddismissmode/uiscrollviewkeyboarddismissmodeondrag?language=objc |
| : scrollable; | ||
|
|
||
| if (keyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag) { | ||
| final FocusNode dismissNode = FocusNode(); |
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.
You don't want to recreate this FocusNode every build, because you need to dispose of it, which (sadly) means you'd need to make this a stateful widget.
But... What's your goal here? Do you just want to know if a context doesn't have focus? If so, you could use something like this, and keep it stateless.
return Focus(
canRequestFocus: false,
child: Builder(builder: (BuildContext context) {
return NotificationListener<ScrollUpdateNotification>(
child: scrollableResult,
onNotification: (ScrollUpdateNotification notification) {
final FocusNode node = Focus.of(context);
if (notification.dragDetails != null && !node.hasFocus) {
node.requestFocus();
}
return true;
},
);
}),
);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.
For the focus node, I'm using that to dismiss the keyboard when scrolling happens. I don't want to spam the Focus every time a scroll event happens so I keep a reference to the dismissNode to make sure that I only requestFocus once. It's fine if the FocusNode gets disposed, it's just a way to filter out duplicate events, if it never worked everything would still look the same.
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.
Where are you disposing it?
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.
I didn't realize we were talking about literally calling .dispose(). I'll rework it.
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.
Started using unfocus instead so no FocusNode instance is needed.
|
@gspencergoog I just wanted to get a gut check that we are fine adding a parameter to ScrollView for the keyboard dismiss behavior. In the future I'd like to add a third option which is "interactive" to duplicate iOS's native behavior. |
| if (notification.dragDetails != null && focusScope.focusedChild != dismissNode) { | ||
| focusScope.requestFocus(dismissNode); | ||
| } | ||
| return true; |
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 probably shouldn't return true here -eating scroll notifications this low in the tree is likely to break people further up who also want to know about scrolling.
| enum ScrollViewKeyboardDismissBehavior { | ||
| /// `manual` means there is no automatic dimissal of the on-screen keyboard. | ||
| /// It is up to the client to dismiss the keyboard. | ||
| manual, |
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.
Apple calls this behavior "none", I chose "manual".
| onNotification: (ScrollUpdateNotification notification) { | ||
| final FocusScopeNode focusScope = FocusScope.of(context); | ||
| if (notification.dragDetails != null && | ||
| (focusScope.focusedChild != null && |
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.
Why do you need this to write this differently from focusScope.hasFocus? And if that's the case, then why wouldn't this work?:
if (notification.dragDetails != null) {
focusScope.unfocus();
}Basically, what's the reason for the extra conditions?
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.
notifcation.dragDetails != null is to make sure the scrolling is the result of a drag, these events get fired too when the scrollview is scrolled as the result of a keyboard showing.
focusScope.focusedChild != null is because I'm going to read from the focusedChild and I don't want to read from a null pointer.
focusScope.focusedChild.hasFocus makes sure I don't spam focusScope.unfocus() and that it only gets called once for all the scrolling events that get sent.
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.
Could I just use focusScole.hasFocus and avoid talking to the focusedChild?
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.
I switched it over to focusScope.hasFocus. It wasn't like this because i found the other way to do it first and it worked.
|
@gspencergoog Oops, i just realized that changing from a Draft to a PR but it didn't remove the "WIP" label. I'm ready to merge this when approved. |
| return NotificationListener<ScrollUpdateNotification>( | ||
| child: scrollableResult, | ||
| onNotification: (ScrollUpdateNotification notification) { | ||
| final FocusScopeNode focusScope = FocusScope.of(context); |
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.
Is there a reason you want to see if the scope is focused, rather than the nearest focus node? It's possible that this ScrollView isn't the focused child of the scope: if there's another child of the scope (i.e. a sibling of the ScrollView), and it's focused, then focusScope.hasFocus would return true, but children of this ScrollView wouldn't have focus.
If you expect the ScrollView to be enclosed in a Focus widget, you could use Focus.of(context).hasFocus. If you don't expect that (which is probably the case), you can introduce a focus node here (probably using a Focus widget with canRequestFocus set to false) and test to see if that has focus, which would mean that one of the children of the ScrollView has focus. You can keep the widget stateless by using a Builder to let you look up the node that the Focus introduces, as in my previous 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.
Here is what I would like to write:
if (Keyboard.isVisible) {
Keyboard.hide();
}
If the scrollview has a sibling that is a TextField that is focused, we want this scrolling action to dismiss his keyboard too. (I think that is the case you are mentioning).
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.
OK, in that case, then looking at the scope is appropriate.
|
@gspencergoog can I get an LGTM? |
gspencergoog
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.
|
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of |

Description
Created an option when creating ListViews and ScrollViews that can control how a ScrollView can automatically dismiss the keyboard (a la UIScrollViewKeyboardDismissMode ).
Related Issues
#21814
Tests
I added the following tests:
Replace this with a list of the tests that you added as part of this PR. A change in behaviour with no test covering it
will likely get reverted accidentally sooner or later. PRs must include tests for all changed/updated/fixed behaviors. See Test Coverage.
Checklist
Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]). This will ensure a smooth and quick review process.///).flutter analyze --flutter-repo) does not report any problems on my PR.Breaking Change
Did any tests fail when you ran them? Please read Handling breaking changes.