title: Why does my Flutter custom ScrollPhysics break GestureDetectors in the Scrollable?
body: <p>Why does my Flutter custom ScrollPhysics break GestureDetectors in my Scrollable? I am using a SingleChildScrollView that uses a custom ScrollPhysics I wrote, and for some reason the GestureDetectors I have in the ScrollView don't react to touch at all, UNLESS the ScrollView is in overscroll. </p>
  
  <p>Basically, unless I just recently scrolled to the scroll extent and the ScrollView is stopped at the scroll extent, I can't detect any gestures inside of the custom physics ScrollView. Detecting gestures inside of a normal ScrollView works just fine, of course.</p>
  
  <p><a href="https://youtu.be/EtalLjr_n-g" rel="nofollow noreferrer">Here's a video of my predicament; the blue ScrollView on the left uses the default ScrollPhysics and the amber one on the right uses my custom ScrollPhysics, with five tappable boxes in each ScrollView:</a></p>
  
  <p><a href="https://github.com/norrisduncan/problems/blob/master/FlutterSingleChildScrollViewAbsorbsGesturesExample" rel="nofollow noreferrer">Here's the GitHub:</a></p>
  
  <p>And here's the code itself:</p>
  
  <pre><code>import 'package:flutter/material.dart';
  import 'package:flutter/physics.dart';
  import 'package:flutter/widgets.dart';
  import 'dart:math' as math;
  
  void main() {
    runApp(FlutterSingleChildScrollViewAbsorbsGesturesExample());
  }
  
  class FlutterSingleChildScrollViewAbsorbsGesturesExample extends StatefulWidget {
    @override
    State&lt;FlutterSingleChildScrollViewAbsorbsGesturesExample&gt; createState() =&gt;
        FlutterSingleChildScrollViewAbsorbsGesturesExampleState();
  }
  
  class FlutterSingleChildScrollViewAbsorbsGesturesExampleState
      extends State&lt;FlutterSingleChildScrollViewAbsorbsGesturesExample&gt; {
    Color colorOfRegularPhysicsBoxOne = Colors.black;
    Color colorOfRegularPhysicsBoxTwo = Colors.black;
    Color colorOfRegularPhysicsBoxThree = Colors.black;
    Color colorOfRegularPhysicsBoxFour = Colors.black;
    Color colorOfRegularPhysicsBoxFive = Colors.black;
  
    Color colorOfCustomPhysicsBoxOne = Colors.black;
    Color colorOfCustomPhysicsBoxTwo = Colors.black;
    Color colorOfCustomPhysicsBoxThree = Colors.black;
    Color colorOfCustomPhysicsBoxFour = Colors.black;
    Color colorOfCustomPhysicsBoxFive = Colors.black;
  
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'An example of how the SingleChildScrollView with custom ScrollPhysics looks like it is eating gestures '
            'meant for its descendants',
        home: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: &lt;Widget&gt;[
            SingleChildScrollView(
              child: Container(
                height: 1400.0,
                width: 200.0,
                color: Colors.lightBlue,
                child: Center(
                  child: Column(
                    children: &lt;Widget&gt;[
                      GestureDetector(
                        onTap: () =&gt; setState(() {
                              colorOfRegularPhysicsBoxOne = Colors.white;
                            }),
                        child: Container(
                          color: colorOfRegularPhysicsBoxOne,
                          height: 100.0,
                          width: 100.0,
                        ),
                      ),
                      GestureDetector(
                        onTap: () =&gt; setState(() {
                              colorOfRegularPhysicsBoxTwo = Colors.white;
                            }),
                        child: Container(
                          color: colorOfRegularPhysicsBoxTwo,
                          height: 100.0,
                          width: 100.0,
                        ),
                      ),
                      GestureDetector(
                        onTap: () =&gt; setState(() {
                              colorOfRegularPhysicsBoxThree = Colors.white;
                            }),
                        child: Container(
                          color: colorOfRegularPhysicsBoxThree,
                          height: 100.0,
                          width: 100.0,
                        ),
                      ),
                      GestureDetector(
                        onTap: () =&gt; setState(() {
                              colorOfRegularPhysicsBoxFour = Colors.white;
                            }),
                        child: Container(
                          color: colorOfRegularPhysicsBoxFour,
                          height: 100.0,
                          width: 100.0,
                        ),
                      ),
                      GestureDetector(
                        onTap: () =&gt; setState(() {
                              colorOfRegularPhysicsBoxFive = Colors.white;
                            }),
                        child: Container(
                          color: colorOfRegularPhysicsBoxFive,
                          height: 100.0,
                          width: 100.0,
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
            SingleChildScrollView(
              physics: CustomSnappingScrollPhysicsForTheControlPanelHousing(stoppingPoints: [
                0.0,
                100.0,
                200.0,
                300.0,
                400.0,
              ]),
              child: Container(
                height: 1400.0,
                width: 200.0,
                color: Colors.amberAccent,
                child: Center(
                  child: Column(
                    children: &lt;Widget&gt;[
                      GestureDetector(
                        onTap: () =&gt; setState(() {
                              colorOfCustomPhysicsBoxOne = Colors.white;
                            }),
                        child: Container(
                          color: colorOfCustomPhysicsBoxOne,
                          height: 100.0,
                          width: 100.0,
                        ),
                      ),
                      GestureDetector(
                        onTap: () =&gt; setState(() {
                              colorOfCustomPhysicsBoxTwo = Colors.white;
                            }),
                        child: Container(
                          color: colorOfCustomPhysicsBoxTwo,
                          height: 100.0,
                          width: 100.0,
                        ),
                      ),
                      GestureDetector(
                        onTap: () =&gt; setState(() {
                              colorOfCustomPhysicsBoxThree = Colors.white;
                            }),
                        child: Container(
                          color: colorOfCustomPhysicsBoxThree,
                          height: 100.0,
                          width: 100.0,
                        ),
                      ),
                      GestureDetector(
                        onTap: () =&gt; setState(() {
                              colorOfCustomPhysicsBoxFour = Colors.white;
                            }),
                        child: Container(
                          color: colorOfCustomPhysicsBoxFour,
                          height: 100.0,
                          width: 100.0,
                        ),
                      ),
                      GestureDetector(
                        onTap: () =&gt; setState(() {
                              colorOfCustomPhysicsBoxFive = Colors.white;
                            }),
                        child: Container(
                          color: colorOfCustomPhysicsBoxFive,
                          height: 100.0,
                          width: 100.0,
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ],
        ),
      );
    }
  }
  
  class CustomSnappingScrollPhysicsForTheControlPanelHousing extends ScrollPhysics {
    List&lt;double&gt; stoppingPoints;
    SpringDescription springDescription = SpringDescription(mass: 100.0, damping: .2, stiffness: 50.0);
  
    @override
    CustomSnappingScrollPhysicsForTheControlPanelHousing({@required this.stoppingPoints, ScrollPhysics parent})
        : super(parent: parent) {
      stoppingPoints.sort();
    }
  
    @override
    CustomSnappingScrollPhysicsForTheControlPanelHousing applyTo(ScrollPhysics ancestor) {
      return new CustomSnappingScrollPhysicsForTheControlPanelHousing(
          stoppingPoints: stoppingPoints, parent: buildParent(ancestor));
    }
  
    @override
    Simulation createBallisticSimulation(ScrollMetrics scrollMetrics, double velocity) {
      double targetStoppingPoint = _getTargetStoppingPointPixels(scrollMetrics.pixels, velocity, 0.0003, stoppingPoints);
  
      return ScrollSpringSimulation(springDescription, scrollMetrics.pixels, targetStoppingPoint, velocity,
          tolerance: Tolerance(velocity: .00003, distance: .003));
    }
  
    double _getTargetStoppingPointPixels(
        double initialPosition, double velocity, double drag, List&lt;double&gt; stoppingPoints) {
      double endPointBeforeSnappingIsCalculated =
          initialPosition + (-velocity / math.log(drag)).clamp(stoppingPoints[0], stoppingPoints.last);
      if (stoppingPoints.contains(endPointBeforeSnappingIsCalculated)) {
        return endPointBeforeSnappingIsCalculated;
      }
      if (endPointBeforeSnappingIsCalculated &gt; stoppingPoints.last) {
        return stoppingPoints.last;
      }
      for (int i = 0; i &lt; stoppingPoints.length; i++) {
        if (endPointBeforeSnappingIsCalculated &lt; stoppingPoints[i] &amp;&amp;
            endPointBeforeSnappingIsCalculated &lt; stoppingPoints[i] - (stoppingPoints[i] - stoppingPoints[i - 1]) / 2) {
          double stoppingPoint = stoppingPoints[i - 1];
          debugPrint(stoppingPoint.toString());
          return stoppingPoint;
        } else if (endPointBeforeSnappingIsCalculated &lt; stoppingPoints[i] &amp;&amp;
            endPointBeforeSnappingIsCalculated &gt; stoppingPoints[i] - (stoppingPoints[i] - stoppingPoints[i - 1]) / 2) {
          double stoppingPoint = stoppingPoints[i];
          debugPrint(stoppingPoint.toString());
          return stoppingPoint;
        }
      }
      throw Error.safeToString('Failed finding a new scroll simulation endpoint for this scroll animation');
    }
  }
  </code></pre>