Flutter: Make ListView bounce at the bottom and clamp at the top position
Asked Answered
A

3

17

There are two types of ScrollPhysics that I want to apply to my ListView. When user reaches the bottom of the List, I want BouncingScrollPhysics() to take place, but when user reaches the top, it shouldn't bounce but rather perform ClampingScrollPhysics(). How to achieve this?

Alexiaalexin answered 19/10, 2019 at 14:58 Comment(0)
N
20

Screenshot:

enter image description here


// create 2 instance variables
var _controller = ScrollController();
ScrollPhysics _physics = ClampingScrollPhysics();

@override
void initState() {
  super.initState();
  _controller.addListener(() {
    if (_controller.position.pixels <= 56)
      setState(() => _physics = ClampingScrollPhysics());
    else
      setState(() => _physics = BouncingScrollPhysics());
  });
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: ListView.builder(
      controller: _controller,
      physics: _physics,
      itemCount: 20,
      itemBuilder: (_, i) => ListTile(title: Text("Item $i")),
    ),
  );
}
Neil answered 19/10, 2019 at 15:17 Comment(4)
I just updated the code, earlier I was using var for _physics (and now updated it to ScrollPhysics) and it was causing error when assigning value to other physics, please check.Neil
I've just relooked at your answer and I have something to add. Before setting the state billion times you should check if the var physics is already assigned to the right one. Otherwise app may lag like crazyAlexiaalexin
Yes, you're right, you can use if-else condition to make sure you only change it when you need. For simplicity, i didn't add them.Neil
For me the list does not scroll anymore. (I'm using pub.dev/packages/draggable_scrollbar)Zaibatsu
H
2

I don't wont to reload every time it change the scroll position. And I hit upon a good one. so I leave it for future readers!

    _controller.addListener(() {
      if (_controller.position.pixels < 0) _controller.jumpTo(0);
    });
Hackler answered 5/4, 2022 at 3:49 Comment(2)
Remember that Stack Overflow isn't just intended to solve the immediate problem, but also to help future readers find solutions to similar problems, which requires understanding the underlying code. This is especially important for members of our community who are beginners, and not familiar with the syntax. Given that, can you edit your answer to include an explanation of what you're doing and why you believe it is the best approach?Bloodstream
oh get off it @JeremyCaney, what are you, a bot? This was helpful.Seavir
C
2

You can create your own custom ScrollPhysics class :

I found one that works great, in the great modal_bottom_sheet package, source: https://github.com/jamesblasco/modal_bottom_sheet/pull/77

ListView(
  physics: const BottomModalScrollPhysics(),
  children: [...],
)
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';

class BottomModalScrollPhysics extends ScrollPhysics {
  /// Creates scroll physics that prevent the scroll offset from exceeding the
  /// top bound of the modal.
  const BottomModalScrollPhysics({ScrollPhysics? parent})
      : super(parent: parent);

  @override
  BottomModalScrollPhysics applyTo(ScrollPhysics? ancestor) {
    return BottomModalScrollPhysics(parent: buildParent(ancestor));
  }

  @override
  double applyBoundaryConditions(ScrollMetrics position, double value) {
    assert(() {
      if (value == position.pixels) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary(
              '$runtimeType.applyBoundaryConditions() was called redundantly.'),
          ErrorDescription(
              'The proposed new position, $value, is exactly equal to the current position of the '
              'given ${position.runtimeType}, ${position.pixels}.\n'
              'The applyBoundaryConditions method should only be called when the value is '
              'going to actually change the pixels, otherwise it is redundant.'),
          DiagnosticsProperty<ScrollPhysics>(
              'The physics object in question was', this,
              style: DiagnosticsTreeStyle.errorProperty),
          DiagnosticsProperty<ScrollMetrics>(
              'The position object in question was', position,
              style: DiagnosticsTreeStyle.errorProperty)
        ]);
      }
      return true;
    }());
    final direction = position.axisDirection;
    // Normal vertical scroll
    if (direction == AxisDirection.down) {
      if (value < position.pixels &&
          position.pixels <= position.minScrollExtent) {
        // underscroll
        return value - position.pixels;
      }

      if (value < position.minScrollExtent &&
          position.minScrollExtent < position.pixels) {
        // hit top edge
        return value - position.minScrollExtent;
      }
    }
    // Reversed vertical scroll
    else if (direction == AxisDirection.up) {
      if (position.maxScrollExtent <= position.pixels &&
          position.pixels < value) {
        // overscroll
        return value - position.pixels;
      }

      if (position.pixels < position.maxScrollExtent &&
          position.maxScrollExtent < value) {
        // hit bottom edge
        return value - position.maxScrollExtent;
      }
    }
    if (parent != null) return super.applyBoundaryConditions(position, value);
    return 0.0;
  }
}
Camorra answered 20/2 at 2:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.