Flutter: Add friction to user scrolling, not spring
Asked Answered
B

2

15

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 heavier for the user.

What I mean by that is when the user drags I want the list to feel like it has tension 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.

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.

List:

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) =>
      buildList(index),
),

Custom scroll class:

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 => 40;

  @override
  double get minFlingVelocity => double.infinity;

  @override
  double get maxFlingVelocity => double.infinity;

  @override
  double get minFlingDistance => 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 < -tolerance.velocity) {
      page -= 0.01;
    } else if (velocity > 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 <= 0.0 && position.pixels <= position.minScrollExtent) ||
        (velocity >= 0.0 && position.pixels >= 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 => 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.

The list is centered on the screen and roughly 60% of the view height.

Bashee answered 10/10, 2020 at 14:51 Comment(3)
In iOS when you drag "beyond the end" of a limit, it behaves precisely as you describe. I'd find a quick solution by simply using that. move your "end" to a position "too short" but then quickly animate the 'end' to the real end once the user actually gets to the end.Solanaceous
What you describe sounds like setting a maximum snap(2nd acceleration derivative). Limiting the snap means that longer scrolling results in (at maximum) a steadily increasing rate of change for acceleration. Alternatively limiting the jerk(1st acceleration derivative) or multiplying the jerk with a factor between 0 and 1 might also have the desired result. I have no idea how to implement that in flutter though.Loralorain
So what you want to do is implement velocity changes over time, so that you then can work with the derivatives of velocity. I have no idea if flutter already provides that or if you need to do that yourself.Loralorain
C
1

If you only need to display one item in the list at a time and the rendering of an item will fit on the screen, using a PageView widget might give you the effect that you are after.

Cradlesong answered 9/11, 2020 at 8:34 Comment(0)
R
1

Try this,

enter image description here

import 'dart:math';
 
import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          home: MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      final _listController = ScrollController();
    
      final List<int> _list = List.generate(12, (index) => index);
    
      ScrollPhysics _scrollPhysics;
    
      @override
      void initState() {
        super.initState();
    
        _listController.addListener(() {
          if (_listController.position.haveDimensions && _scrollPhysics == null) {
            setState(() {
              var dimension =
                  _listController.position.maxScrollExtent / (_list.length - 4);
              _scrollPhysics = CustomScrollPhysics(itemDimension: dimension);
            });
          }
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: SafeArea(
            child: ListView.builder(
              scrollDirection: Axis.vertical,
              controller: _listController,
              physics: _scrollPhysics,
              itemCount: _list.length,
              itemBuilder: (context, index) => Container(
                height: 300,
                width: double.infinity,
                color: randomColor,
                child: FlutterLogo(),
                margin: const EdgeInsets.all(2.0),
              ),
            ),
          ),
        );
      }
    
      Color get randomColor =>
          Color((Random().nextDouble() * 0xFFFFFF).toInt() << 0).withOpacity(1.0);
    }
    
    class CustomScrollPhysics extends ScrollPhysics {
      final double itemDimension;
    
      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 < -tolerance.velocity) {
          page -= 0.1;
        } else if (velocity > tolerance.velocity) {
          page += 0.1;
        }
        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 a page boundary.
        if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
            (velocity >= 0.0 && position.pixels >= 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(spring, position.pixels, target, velocity,
              tolerance: tolerance);
        return null;
      }
    
      @override
      bool get allowImplicitScrolling => false;
    }

now in flutter, there is no pre define method for friction, but this is possible with some custom way.

Renaissance answered 16/11, 2020 at 6:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.