Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to force ScrollController to recalculate position.maxExtents?

I'm trying to create a desktop-style scrollbar, that changes it's size based on the size of the content. My scrollbar shares a ScrollController with a list, and relies on the position.maxExtents to know how large the content area is.

The issue is that when I change the number of rows, the maxExtents will not update, until a scrollEvent is initiated.

I've worked around it with code like this, moving 1px up, and 1px down over 100ms:

widget.controller.jumpTo(controller.position.pixels + 1);
Future.microtask(() => widget.controller.animateTo(controller.position.pixels - 1, duration: 100.milliseconds, curve: Curves.linear));

Which works pretty quite well when the list can scroll. However, when the list is < the height of the view, it can't scroll, and these calls have no effect, and I'm stuck with a stale maxExtents.

How can I just tell the list: "Hey, list, recalculate your children!"?

like image 563
shawnblais Avatar asked Mar 25 '20 19:03

shawnblais


3 Answers

You can delay your code to when the controller has been updated using the following.

WidgetsBinding.instance.addPostFrameCallback((_) {
  ...your code which requires controller's max extents...   
});
like image 124
ltk Avatar answered Oct 18 '22 18:10

ltk


Aside from the issues you've mentioned, animating the scroll position will cause Flutter to draw frames unnecessarily.

Borrowing from @ltk and @Rakaton, I think there's a simpler and more efficient way to do this:

// Run this code whenever the layout changes (i.e. any time you setState
// to update the list content)
WidgetsBinding.instance?.addPostFrameCallback((_) {
  widget.scrollController.position.notifyListeners();
});

You may also want to wrap your list component in a SizeChangedLayoutNotifier to detect size changes from things like window resize:

NotificationListener<SizeChangedLayoutNotification>(
  onNotification: (notification) {
    WidgetsBinding.instance?.addPostFrameCallback((_) {
      scrollController.position.notifyListeners();
    });
    return true;
  },
  child: SizeChangedLayoutNotifier(
    child: ListView(
      // ...
    ),
  ),
);
like image 41
Joshua Wade Avatar answered Oct 18 '22 18:10

Joshua Wade


I think you should consider to use force pixels, but it's a protected method so it's gonna give you a warning. And I don't know about the performance or another stuffs about it.

widget.controller.position.forcePixels(controller.position.pixels + 1);

or combination of correct pixels and notifylistener.

widget.controller.position.correctPixels(controller.position.pixels + 1);
widget.controller.position.notifyListeners();
like image 37
Rakaton Avatar answered Oct 18 '22 18:10

Rakaton