I've recently had to hide the keyboard on scroll in my app. Once the user has finished typing, they typically scroll down. By hiding the keyboard, they gain a lot of real estate.

First try: ScrollController

My view uses a SingleChildScrollView to show scrollable content. My first try was to introduce a ScrollController and listen to it:

ScrollController scrollController;

@override
void initState() {
  scrollController = ScrollController();
  scrollController.addListener(hideKeyboard);
  super.initState();
}

@override
Widget build(BuildContext context) {
  return SingleChildScrollView(
    controller: scrollController,
    child: Column(...)
  );
}

hideKeyboard() {
  FocusScope.of(context).requestFocus(FocusNode());
}

And it worked great!... Most of the time.

There are times where it will hide the keyboard even though I am not actively scrolling. For example if I select a slightly hidden TextField, the keyboard will briefly appear, and then disappear as the TextField scrolls into view.

Second try: GestureDetector

@override
Widget build(BuildContext context) {
  return GestureDetector(
    onVerticalDragDown: (_) {
      hideKeyboard();
    },
    child: SingleChildScrollView(...));
}

I've been wondering if I should change the behavior of the GestureDetector. The doc says it defaults to HitTestBehavior.deferToChild, which means that the GestureDetector will handle the event if the touch event hits one of its children. That's what we want. So we can keep the default.

Also, I initially used onVerticalDragStart. The doc says it'll trigger when "[it] has begun to move vertically." but it didn't work. I had to use onVerticalDragDown. My guess is that the scroll is done its child, the SingleChildScrollView, not by the GestureDetector itself, so the vertical drag will technically never "start".