title: Flutter: Add friction to user scrolling, not spring
body: <p>I have a list view with a custom scroll physics class that defines how I want the scrolling and spring effects of a list I have. I have managed to set the spring dampening the way I want it but I can't seem to find any settings around the making the drag <em>heavier</em> for the user.</p>
  <p>What I mean by that is when the user drags <strong>I want the list to feel like it has tension</strong> so the user needs to drag further than they normally would I and I will handle the movement to the next item in the list using the custom scroll physics.</p>
  <p><strong>I want it to feel like a turn style at a train station. There is lots of tension you receive with your body and once you pass through the turn style resets itself to center.</strong></p>
  <p>List:</p>
  <pre><code>child: ListView.builder(
    cacheExtent: MediaQuery.of(context).size.height * 2,
    padding: EdgeInsets.only(top: 0),
    itemExtent: constraints.maxHeight -
        SizeConfig.blockSizeVertical * 40,
    itemCount: channelList?.length ?? 0,
    controller: _controller,
    physics: _physics,
    itemBuilder: (BuildContext context, int index) =&gt;
        buildList(index),
  ),
  </code></pre>
  <p>Custom scroll class:</p>
  <pre><code>import 'package:flutter/material.dart';
  import 'package:flutter/physics.dart';
  
  class CustomScrollPhysics extends ScrollPhysics {
    final double itemDimension;
    static final SpringDescription customSpring =
        SpringDescription.withDampingRatio(
      mass: 4,
      stiffness: 150.0,
      ratio: 2.0,
    );
  
    @override
    double get dragStartDistanceMotionThreshold =&gt; 40;
  
    @override
    double get minFlingVelocity =&gt; double.infinity;
  
    @override
    double get maxFlingVelocity =&gt; double.infinity;
  
    @override
    double get minFlingDistance =&gt; double.infinity;
  
    CustomScrollPhysics({this.itemDimension, ScrollPhysics parent})
        : super(parent: parent);
  
    @override
    CustomScrollPhysics applyTo(ScrollPhysics ancestor) {
      return CustomScrollPhysics(
          itemDimension: itemDimension, parent: buildParent(ancestor));
    }
  
    double _getPage(ScrollPosition position) {
      return position.pixels / itemDimension;
    }
  
    double _getPixels(double page) {
      return page * itemDimension;
    }
  
    double _getTargetPixels(
        ScrollPosition position, Tolerance tolerance, double velocity) {
      double page = _getPage(position);
      if (velocity &lt; -tolerance.velocity) {
        page -= 0.01;
      } else if (velocity &gt; tolerance.velocity) {
        page += 0.01;
      }
      return _getPixels(page.roundToDouble());
    }
  
    @override
    Simulation createBallisticSimulation(
        ScrollMetrics position, double velocity) {
      // If we're out of range and not headed back in range, defer to the parent
      // ballistics, which should put us back in range at an item boundary.
      if ((velocity &lt;= 0.0 &amp;&amp; position.pixels &lt;= position.minScrollExtent) ||
          (velocity &gt;= 0.0 &amp;&amp; position.pixels &gt;= position.maxScrollExtent))
        return super.createBallisticSimulation(position, (velocity));
      final Tolerance tolerance = this.tolerance;
      final double target = _getTargetPixels(position, tolerance, velocity);
      if (target != position.pixels) {
        return ScrollSpringSimulation(
            customSpring, position.pixels, target, velocity,
            tolerance: tolerance);
      }
      return null;
    }
  
    @override
    bool get allowImplicitScrolling =&gt; false;
  }
  
  /// Note: This Widget creates the ballistics model for a snap-to physics in a ListView.
  /// Normally you might use the ListViewScrollView, however that widget currently
  /// has a bug that prevents onTap detection of child widgets.
  
  </code></pre>
  <p>The list is centered on the screen and roughly 60% of the view height.</p>