flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
163.57k stars 26.9k forks source link

ListView: Poor performance with many variable-extent items + jumpTo (scroll bar, trackpad, mouse wheels) #52207

Open esDotDev opened 4 years ago

esDotDev commented 4 years ago

Update September 2023:


Update from July 2023: https://github.com/flutter/flutter/issues/52207#issuecomment-1648672384


Update from September 2021: After much discussion, this https://github.com/flutter/flutter/issues/52207#issuecomment-782708343 suggestion emerged as a pretty good proposal. Basically, SliverList could short-circuit actually instantiating and laying out children if, once built, they are discovered to implement the PreferredHeight protocol. The next step is to implement this. -@Hixie


[√] Flutter (Channel master, v1.15.4-pre.241, on Microsoft Windows [Version 10.0.18362.657], locale en-US)

Here is an example (~80 lines) of a 10,000 item ListView, which attempts to uses a simple scrollbar to drive scroll position: https://gist.github.com/esDotDev/792425c2cdfef947ce514b8ab70511e6

The performance is very poor on all platforms, Desktop, Web, Android, even when using profile mode on Android. iOS has not been tested.

I'm seeing frame render times up to 1300ms on Windows 10, with a Ryzen 3700x. Android, in profile mode, with a Snapdragon 845, has similar results. Visually, you can easily produce a 2-3 second lag by just scrolling up and down a couple of times. http://screens.gskinner.com/shawn/2020-03-08_12-57-31.mp4

Setting the .itemExtent property solves the problem, but severely limits the flexibility of the list, no expanding cards, no ability to size rows to content, etc.

Problem in a nutshell We need the ability to have many list items, of variable height, while retaining the ability to set scroll position efficiently. This is especially important on Web and Desktop which typically use scrollbars to traverse lists.

Proposed Solutions

  1. It would be nice if the list could handle this itself, seems like it calculates maxExtents each frame, when we really only need it calculating when the size of something has changed. Potentially this could be exposed so we could call it manually (scrollController.calculateExtents()) when we know something is changing.

  2. As a simpler workaround, you could expose .maxExtents so I as the programmer of the list can specify max extents with some basic math. ie, if I know my open cards are 40px heigh, and my closed are 20px, I can track open/close cards, and provide the list with the max extents very quickly, without it needing to measure.

  3. Could potentially leverage PreferredSizeWidget to reduce the calculation costs for the list. Then each list item can return it's own size, and the developer can come up with novel ways to reduce that calculation cost. For example, I could have my closed items all return a hardcoded value, while my "expanded items" use a LayoutBuilder to calculate their height. Or I could simply have multiple hardcoded values, and return the right one depending on the state of the renderer.

dnfield commented 4 years ago

I think the core problem here is that, without knowing the item extent is fixed, we have no way of knowing that it won't dynamically change on each scroll, and so have to do all the work to build all the intermediate items so we can figure out scroll position and keep the scrollbar accurate.

I suspect if you really profiled this down to the expensive methods, it's not just in calculating the max extents, but also in calculating the extent of each sliver that has to be rendered for the screen as you jump 1000s of items in the list. With a fixed extent list we can take a bunch of shortcuts. With a variable extent we can't.

I suspect that suggestion 1 would not help because scrolling means something has changed, and we have no way of knowing that the application didn't decide to make certain items bigger or smaller as you scroll (imagine an expansion card that collapses when the user starts scrolling for example).

Suggestion 2 would not help because we'd still need to calculate all the actual extents when you make a very large jump.

Suggestion 3 might actually make things more expensive when you're using layout builders, since those will require a speculative layout pass in addition to the actual layout pass.

We might be able to fudge something here and check how fast you're scrolling, and take some lower fidelity shortcut when scrolling very rapidly. But we'd have to really profile this to know where exactly the expensive parts are first.

dnfield commented 4 years ago

At your application level, you might be able to do some similar shortcut, e.g. rebuild the listview with a fixed extent once the user starts scrolling fast, and then rebuild it again with a variable extent once they slow down/stop. But this would probably result ins ome weird jumpiness of the scroll bar.

esDotDev commented 4 years ago

Thanks for the thoughts, given the performance I'm seeing, > 1000ms frame times, it feels like a speculative pass that takes a ms or two, could be a viable tradeoff?

Those were just my initial ideas, I'm sure theres many others. They key takeaway I think is that the algorithm chosen for drag-based scrolling on very large lists is not performant when used on desktop/web with scrollbar-based control.

What I was trying to get at with my solutions, were to somehow offload the calculations to the renderer, so they could either be cached, or calculated by the list item, rather than having to go through the expensive layout pass. Obviously it would be ideal if the layout pass was cheap enough to just work, but if not, this could be an alternative approach.

"we have no way of knowing that it won't dynamically change on each scroll, " To this end, it would be nice if there was someway for the application to inform the list that no, we will not change things during scroll, and in the rare case we we do, we will let you know so you can re-calculate. Then, in a worst-case scenario, we get what we have now... but in most cases, it would run really well.

[Edit] Tried setting itemExtents while dragging large amounts, and removing when dragging small. Doesn't really work, it runs smooth initially, but then just grinds to a halt as soon as extents is removed.

firatcetiner commented 4 years ago

[Edit] Tried setting itemExtents while dragging large amounts, and removing when dragging small. Doesn't really work, it runs smooth initially, but then just grinds to a halt as soon as extents is removed.

May I ask how you implement such a logic? @esDotDev

VladyslavBondarenko commented 4 years ago

Related to https://github.com/flutter/flutter/issues/24791

modulovalue commented 4 years ago

Here's a second, simpler example that reproduces this issue. (copied over from #54195)

video (tested on macos)

import 'package:flutter/material.dart';

void main() {
  runApp(MyHomePage());
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: ListView.builder(
          itemCount: 10000,
          itemBuilder: (context, index) => Text("$index"),
        ),
      ),
    );
  }
}
leszekkrol commented 4 years ago

At the moment I have the same issue on severals projects. I noticed that problem is more apparent when you add physics: BouncingScrollPhysics() to ListView.builder.

firatcetiner commented 4 years ago

I'm offering a workaround related to this issue in here. I hope it will be useful for you.

esDotDev commented 4 years ago

That looks promising, thanks!

klaszlo8207 commented 4 years ago

Any news on this? Any workaround??

esDotDev commented 4 years ago

I tried that workaround and it made things much worse.

ItemExtent works, but a very large list with dynamic heights in Flutter is not really doable with the current implementation of ListView/ScrollController + JumpTo. There's just way too much constant measurement occurring.

firatcetiner commented 4 years ago

We have an update on the stable channel (1.17), I don't see any improvements on this topic, still sticking with the workaround.

azeemgujjar commented 4 years ago

This is the major bug in flutter, a very clear performance mirror as compared to react-native. Flutter vertical scrolling specially with images have jerking issues.

here is an example app on playstore (Flutter demo: CINEMA) you can download and test, but that i am sure its flutter bug because all similar apps or with long list views this happens.

More examples in flutter official app: Flutter Gallery

Flutter Gallery -> (Crane A personalized travel app) -> vertical scroll listview in bottom (Jerk is clearly visible)

Flutter Gallery -> (Rally A personalized finance app) -> Login -> vertical scroll it (choppy scrolling Jerks)

Above examples are not debug all are on production playstore, on stackoverflow this is under discussion but everyone is doing just tricks which is not appropriate way to develop an production grade application.

firatcetiner commented 4 years ago

We have an update on the stable channel (1.17), I don't see any improvements on this topic, still sticking with the workaround.

Well I have suggested a workaround but it seems there is much greater issue related to the scrolling.

One thing I noticed that is in every issue there is a member saying "Please include a small reproducable peace of code.".

The issue is literally can be seen on Flutter Gallery app, please don't copy paste same line as the mentioned bug literally occurs in the very simple scenarios like in the Flutter Gallery: Crane.

azeemgujjar commented 4 years ago

We have an update on the stable channel (1.17), I don't see any improvements on this topic, still sticking with the workaround.

Well I have suggested a workaround but it seems there is much greater issue related to the scrolling.

One thing I noticed that is in every issue there is a member saying "Please include a small reproducable peace of code.".

The issue is literally can be seen on Flutter Gallery app, please don't copy paste same line as the mentioned bug literally occurs in the very simple scenarios like in the Flutter Gallery: Crane.

100% i agree, everyone can see that bug in their own flutter gallery app, i don't know why they are asking for piece of code.. Even after this response i start developing my projects on react-native because its big risk to put million dollars on flutter right know. Very basic widgets are buggy.

vishnukvmd commented 4 years ago

Jank while scrolling large lists is a decade old problem in client side development. This being assigned a P4 is disheartening.

dnfield commented 4 years ago

This problem is relatively easy to solve for fixed extent lists. It's much harder to solve efficiently for variable extent lists.

I think what we'll need to do - which is what other platforms do - is to cheat on the scrollbar positioning. A few people have started to look at this over the last couple years and not finished it.

This is a very solvable problem, but it's not as easy as it first seems. It's related to https://github.com/flutter/flutter/issues/31637 and https://github.com/flutter/flutter/issues/12319 - which was closed with a package available. Can you see if the package solves your needs?

dnfield commented 4 years ago

Specifically, https://github.com/google/flutter.widgets/tree/master/packages/scrollable_positioned_list

/cc @tarobins

azeemgujjar commented 4 years ago

This problem is relatively easy to solve for fixed extent lists. It's much harder to solve efficiently for variable extent lists.

I think what we'll need to do - which is what other platforms do - is to cheat on the scrollbar positioning. A few people have started to look at this over the last couple years and not finished it.

This is a very solvable problem, but it's not as easy as it first seems. It's related to #31637 and #12319 - which was closed with a package available. Can you see if the package solves your needs?

All these tweaks i already go through, as i already mentioned tricks is not the solution. Flutter team should accept and fix this bug on high priority. Apps links i shared are production apps even 1 app is flutter official app and bug still existed in it.

Just try to make Google Photos like gallery app with just 1000 photos, and flutter sucks.. same experiment i did with react-native with 10K photos it works like charm no jerking or any wrong user experience.

dnfield commented 4 years ago

The Flutter team is a highly distributed group, all of whom are pretty busy with important issues. The best way to show the importance of this issue is to give it a thumbs up on the top level comment, which raises visibility to the triage process.

Anyone is welcome to write a doc or open a patch on how to fix this :) It's mainly a matter of finding someone interested enough and with the time to do it.

firatcetiner commented 4 years ago

This problem is relatively easy to solve for fixed extent lists. It's much harder to solve efficiently for variable extent lists.

I think what we'll need to do - which is what other platforms do - is to cheat on the scrollbar positioning. A few people have started to look at this over the last couple years and not finished it.

This is a very solvable problem, but it's not as easy as it first seems. It's related to #31637 and #12319 - which was closed with a package available. Can you see if the package solves your needs?

I think the main issue is the performance of the scroll behavior. It is understandable the performance won't be good as the fixed extend lists but it should not cause any jank when dealing with dynamic sized children on Listview, especially with the simple widgets.

The Flutter team is a highly distributed group, all of whom are pretty busy with important issues. The best way to show the importance of this issue is to give it a thumbs up on the top level comment, which raises visibility to the triage process.

How about #22314? This was closed as there are lots of comments causing a "noise". I think there is an endless loop here: Client opens a new issue, there are many comments regarding of the issue, Flutter Team decides to close the issue and propose to open a new issue. Anyone is welcome to write a doc or open a patch on how to fix this :) It's mainly a matter of finding someone interested enough and with the time to do it.

Yeah, clients would definitely figure out how to solve this problem and consider being a collaborator instead of developing their own Flutter application.

dnfield commented 4 years ago

@firatcetiner - that issue is similar to this one but had a different root cause. It's possible that issue was meant to include this one, but in any case it's better to have issues that are more focused on specific problems than umbrella issues (e.g. it's better to have an issue about the touch input aspect and another issue about the layout speed aspect).

My understanding is that this issue covers the layout speed aspect, specifically when a scrollbar is used. No one is suggesting closing this issue, because it's not fixed :)

And if you're not interested in working on this issue that's fine - someone else will at some point. It's just a matter of when, and how many other issues exist that are higher priority (as determined by customer demand) than this one.

vishnukvmd commented 4 years ago

Specifically, https://github.com/google/flutter.widgets/tree/master/packages/scrollable_positioned_list

Thanks for suggesting the workaround. However, it does not address the use case of using a scrollbar: https://github.com/fluttercommunity/flutter-draggable-scrollbar/issues/2.

vishnukvmd commented 4 years ago

The best way to show the importance of this issue is to give it a thumbs up on the top level comment, which raises visibility to the triage process.

it's better to have issues that are more focused on specific problems than umbrella issues

I completely agree with the first statement. But if a certain problem is triggered by a combination of underlying problems, I don't thing the best way to solve it is to break it down into Github issues and to solve them based on the amount of traction each of them receive. The root problem might never get fixed.

At the end of the day Flutter is a product, and it makes life difficult for consumers when the team does not take ownership of larger problems and treats it like a code-base instead.

esDotDev commented 4 years ago

I don't really understand the pushback here. The current behavior is extremely inefficient when moving large distances in the list. So, something needs to be changed in the ScrollController no?

"With a fixed extent list we can take a bunch of shortcuts."

We as the app developer (working in a specific domain) can take many more shortcuts than you (working in the general), if you provide us the means. If the 3 original proposals don't work, can you offer one that does? How can we provide the hinting for you??

This is the core of the issue. Solving the general use case for measuring variable extent renderers is a cpl orders of magnitude harder than doing it inside the specific app domain. Help us, to help you, to help us :)

dnfield commented 4 years ago

I'm not quite clear on who's pushing back on anything. I think everyone involved in this thread agrees there's a bug that needs to be fixed.

esDotDev commented 4 years ago

Fair. But I'm not sure if we're working towards the ability for app developers to have more control, or chasing a more optimized internal approach.

vishnukvmd commented 4 years ago

FWIW, the solution proposed in https://github.com/flutter/flutter/issues/12319#issuecomment-569296423 seems to be setting the cacheExtent property to inflate all the list items, even those that are outside the current viewport.

This will be very expensive for large lists with non-trivial widgets.

davidmartos96 commented 4 years ago

Maybe related #28204 I've recently profiled one list view in my app that wasn't performing that well. Many frames were taking too long because of a Semantics step. Putting an ExcludeSemantics above my list turned out to be day and night in terms of scrolling performance. Obviously, not ideal to turn off a11 features. Just wanted to share in case it is related or it helps someone.

firatcetiner commented 4 years ago

@davidmartos96 Thank you for sharing.

Yesterday I tried wrapping child widgets in the ListView with TweenAnimationBuilder using with a Transform.scale() just for fun, not a big deal. I don't know why but the scroll performance is much better now. I remember someone else has mentioned this behavior few months ago but I couldn't find the exact comment.

I am going to investigate why this has an impact on scroll performance in my spare time.

azeemgujjar commented 4 years ago

@davidmartos96 Thank you for sharing.

Yesterday I tried wrapping child widgets in the ListView with TweenAnimationBuilder using with a Transform.scale() just for fun, not a big deal. I don't know why but the scroll performance is much better now. I remember someone else has mentioned this behavior few months ago but I couldn't find the exact comment.

I am going to investigate why this has an impact on scroll performance in my spare time.

best way to test its performance, simply try to create image gallery like, google photos. i tested in both flutter and react-native.. and results are horrible.. in react its smoothness and scroll experience is exactly as native, no one can judge its react, but flutter after 30-40 photos you will feel its not smooth at all, it jerks also sometimes screen flicker. i don't know exact english terms to explain. but if you will try with different speed of scrolling, randomly move up down etc you will feel huge difference.

There is only way flutter can satisfy they must release official demo app of image gallery.. 😄

azeemgujjar commented 4 years ago

Maybe related #28204 I've recently profiled one list view in my app that wasn't performing that well. Many frames were taking too long because of a Semantics step. Putting an ExcludeSemantics above my list turned out to be day and night in terms of scrolling performance. Obviously, not ideal to turn off a11 features. Just wanted to share in case it is related or it helps someone.

Seems flutter should change the statement "Flutter is Google’s UI toolkit for building beautiful , natively compiled applications for mobile, web, and desktop from a single codebase."

performance and user experience is everything, otherwise nobody care either its running on ARM or Converting Javascript to Native..

liyuqian commented 4 years ago

@vishnukvmd @esDotDev : I believe the ScrollablePositionedList suggested by @dnfield will have a dramatic speedup for your scroll bar use case. See the demo code at https://gist.github.com/liyuqian/6fe7a6be4b5035bbdbdba57d3356b43d

As Flutter is making web and desktop its first-class platforms on par with mobile, I feel that having ScrollablePositionedList as a 3rd party package is probably insufficient. We should perhaps add it or something similar directly to the Flutter framework. It's so common to use scroll bars, trackpads, and mouse wheels on web/desktop to jump to a scroll position super fast. It's very different on mobile where the fastest touch scroll speed is just one screen per input event sampling period (which is usually 1/60 second). CC @Hixie @goderbauer to see if introducing this in the framework makes sense.

BTW, I would not call a solution such as ScrollablePositionedList as "cheat on the scrollbar positioning" because there's still a nice 1:1 mapping between scroll bar and the scroll list position, and you can always expect scroll x, scroll y, scroll -(x + y) to get you back to the original position.

Similar approaches could also be applied to the use case in https://github.com/flutter/flutter/issues/52207#issuecomment-610486875 without a scroll bar (I believe the fast jump there is triggered either by trackpad or mouse wheels). When Flutter framework detects a very large jump (e.g., scrolling more than 1 screen of pixels within 1 frame), it no longer jumpTo by pixel offsets, but instead jumpTo by item index differences. The index difference could be calculated by something like pixelOffset / averageItemExtentOnScreen.

Our first step towards fixing this might be to add the sample code provided by @esDotDev and @modulovalue as Flutter benchmarks, so we can track our progress and avoid future regressions once we fixed it. So thank you for providing the sample code, and please feel encouraged to follow https://github.com/flutter/flutter/wiki/How-to-write-a-render-speed-test-for-Flutter to directly write a PR to add your sample code as a Flutter performance test!

dnfield commented 4 years ago

@liyuqian the "cheat on scroll bar position" comment is if we try to make the current default implementation of ListView etc. work better for scroll bars.

For example, we could estimate the extent of the ListView based on what's currently laid out within its cache extents. But this won't be accurate for lists that currently have very large or very small items in the extent compared to the rest of the list - so the scroll bar would have to "jump around" as you scrolled, appearing to near the end and then actually adding more, or appearing to be very far from the end and then suddenly more rapidly getting closer. You can observe this effect in scroll views on native platforms today if they're infinite (or at least dynamically loading). For example, as you scroll down the Facebook timeline page the scrollbar jumps around.

esDotDev commented 4 years ago

@vishnukvmd @esDotDev : I believe the ScrollablePositionedList suggested by @dnfield will have a dramatic speedup for your scroll bar use case. See the demo code at https://gist.github.com/liyuqian/6fe7a6be4b5035bbdbdba57d3356b43d

I don't think so, since this only scrolls by index, but we are desiring a scrollbar that scrolls using pixel values as is expected in most lists on both desktop and mobile.

It's frustrating as the sizing info for non fixed lists is usually trivial to calculate in the app domain, but the framework locks us out from trying to help it, so instead it just spins its wheels doing millions of pointless layout calculations.

liyuqian commented 4 years ago

@dnfield : it seems that we're already "cheating" in that sense due to _extrapolateMaxScrollOffset. I think we could use the same extrapolation logic to quickly jump to a new offset that's either far above or far below the current offset. A jump-around scroll bar seems to be much more acceptable than a non-responsive list.

@esDotDev : I agree that allowing developers to send us a list of item sizes will surely make our implementation much easier and faster. Allowing ListView to take a List<double> itemExtents instead of a single double itemExtent seems to be simplest API change I can think of. However, it also increases our API surface, maintenance cost, and developers' burden to maintain or calculate that list. It would be even better if we can achieve fast jump without that list or any modification to our existing APIs, but that may force us to have a jump-around scroll bar as other platforms have. @dnfield @Hixie @goderbauer : what's your thought of allowing developers to provide ListView a list of extents?

dnfield commented 4 years ago

You can already provide a the extent if it's fixed. If it's not fixed, I'm not clear what mechanism would be helpful here. It'd be really helpful to see a test or concrete use case.

It just has to support lazily loading/building the elements - but I think as soon as you allow that to happen and allow for variable sized slivers, you'll run into this problem where you just have to estimate it and try to get as close as possible with what's available in the cache extent.

esDotDev commented 4 years ago

The use case here is variable height extents, but the developer can cheat and provide those to you, so you that you do not need to measure via layout.

Use Case 1 I have renderers with 2 states, open and closed, I know my closed are 40 my open are 100. I can simply track the state of each renderer in an array, and provide you the list of extents for each. No measuring needed, no layout needed, just pure math. I can take this a step further, and have animated extents that update each frame as a specific renderer is opening or closing. You can now easily and cheaply jump to any pixel position, and know exactly which renderers should be there.

Use Case 2 I have renders with several rows of data (name, address, country, email etc), not each renderer has all data, some are 1 row, some are 4. Each row is 20px. I can easily determine how many rows each render takes (by just looking at my data) and pass that size into the list, so the list again, does not need to do a single measurement, while my list retains the ability to have renders of 4 different sizes.

Use Case 3 I have a very large list, each row is 100px tall, I also have a few "Header" rows, that are 30px each. I could simply provide you the height, based on whether widget.isHeader=true, saving the list all of that measuring. Otherwise this very basic use case is simply not achievable.

@liyuqian I agree this API sounds simple and effective for the use case. I don't think the general case is actually solvable, and I also don't think developers expect to have 5000 variable height items, at 60fps without doing a little bit to help the framework, we realize this is not realistic. It's totally fine to expect them to do a little extra work to get performance here imo, we will be happy to if it gets the end result we need.

And in case you think this is a made up use case, we recently built a Google Contacts App for desktop, you can have 25,000 emails in a google contacts account, and we would really have liked to have variable height renderers but could not because the list + scrollbar could not be trusted to perform at higher renderer counts.

esDotDev commented 4 years ago

@liyuqian To address the developer effort, I believe as you are proposing, List<double> ItemExtents would be optional ya? Otherwise it will fallback to the layout method? That seems very reasonable.

roman-petrov commented 4 years ago

I just created a simple JsFiddle example (in new window) that I believe illustrates the case. I tested in in all major browsers (Chrome, Firefox, Edge) and it seems that it has good scrolling performance. Items have variable heights but I did not provide any itemExtents.

It seems that browsers defer layout calculation for long lists and give maximum priority to have scroll thumb moving with 60+ FPS. Can we have an easy way to develop a similar case in Flutter without providing itemExtents and still having good scrolling performance?

liyuqian commented 4 years ago

@dnfield : I just coded a sample app LongListVariableExtent https://github.com/flutter/flutter/pull/61594/files that's similar to @esDotDev 's open-or-closed-item case. It also includes a potential RenderSliverFixedVariableExtentList implementation to demonstrate the speed difference. It's quite simple as it reuses most of RenderSliverFixedExtentBoxAdaptor.

For a more polished implementation, I think we can either (1) use binary search to further speed it up when the list itemExtents is mostly unchanged, or (2) change the List<double> itemExtents to a generator double itemExtent(int index) for more dynamic cases and avoid the big initialization cost.

@roman-petrov : theoretically I don't think there's anything that prevents Flutter to match browser's behavior and performance. We can even avoid the black flash and just show the list scrolling as fast as possible but not being able to catch up with the scrollbar until a couple hundred milliseconds later. But that seems to be a more complicated change than either to extrapolate the extent for fast jump, or to let developers send in List<double> itemExtents.

roman-petrov commented 4 years ago

@dnfield , I understand that implementing browser-like scrolling behavior and performance might be more complicated. But IMHO this also can give more value in terms of Flutter usability & performance because Flutter users will not have to specify any itemExtents to implement efficient scrolling.

Moreover, in many cases (including the case in my JsFiddle) itemExtents are not known at compile time and require layout computation. So, if I understand correctly, it would not be possible to improve scrolling performance using itemExtents when ListView items have "dynamic" heights.

esDotDev commented 4 years ago

Its true that it's not always practical for even the developer to compute the size, for example paragraphs of multi-line text. So passing sizes is not a silver-bullet, just a very nice tool in the toolbox.

Even with the above example though, a renderer can actually paint the text, and measure it once and from then on return it's cached height.

ilijaNL commented 3 years ago

This is actually a critical problem in my view for flutter. Unfortunately I can't get my head around how flutter exactly handles the viewport + scrolling.

In my view the scroll controller is missing the following methods:

By providing such cheap methods, you can implement a datastructure where you can efficiently jump to any index of the list by actually modifing the under laying data which correspond to the indices. This is demonstrated here: https://dartpad.dev/efcc5d97ff9db42c3f17d2310695282f . Try to inspect the logs when pressing the buttons at the bottom.

However the performance penalty occurs when you press "jump to 500" and scroll up, say 100 items. After pressing "jump to 500" again, it renders all the "scrolled" items again which is actually causing the biggest performance penalty (#24791).

Another point I want to mention is when dealing with items with dynamic height an "cheap" approximation for extents should be used, consider whatsapp as example. Something like viewportSize/itemsInViewPort * totalItems

philipgiuliani commented 3 years ago

Hi @liyuqian, I really like your idea. Do you think that it would be possible to extract the LongListVariableExtent into a library for now?

Hixie commented 3 years ago

One solution would be to do something similar to SliverPrototypeExtentList but with multiple prototypes, where each item in the list has a way to report which prototype it'll be like. That would work if the children aren't all arbitrarily sized but instead all fit into a small number of sizes. (It could even be adjusted to support the occasional special case where we'd have to measure it.)

Fundamentally the problem here isn't the scroll bar, it's just that to scroll to a position, we need to know what's at that position, and to do that, we have to know how big everything is before that point. If we scroll to position 10,000, we have to lay out 10,000 pixels' worth of widgets to figure out which one is at that position.

vishnukvmd commented 3 years ago

@Hixie To piggy back on what @esDotDev proposed earlier, would it help if each list item could optionally set its width and height attributes so that we don't actually have to lay out all the pixels to compute which item is going to be at what position?

Hixie commented 3 years ago

If you know the precise height of each item then it's trivial. Just copy all the code that currently implements FixedExtentList and make it be able to get the height of each item when it does its math, without having to actually build or lay out the children.

esDotDev commented 3 years ago

If you know the precise height of each item then it's trivial. Just copy all the code that currently implements FixedExtentList and make it be able to get the height of each item when it does its math, without having to actually build or lay out the children.

Is the suggestion that developers should do this everytime they want this ability? This may be trivial to the Flutter team, but there's no way your typical developer will have any clue what FixedExtentList even is. In practice it's more like: "Just understand the entire inner workings of the flutter scroll implementation, and then copy all the code that currently implements FixedExtentList ".

My argument is that quite often the developers do know the height of their 'variable' content, or they can devise some fairly simple system to figure it out. The framework should just be friendly to this concept that it needs help here, and to let us provide the hints. In a simple way that doesn't force us to dive into thousand line class files. I described some examples here, of course they are literally endless: https://github.com/flutter/flutter/issues/52207#issuecomment-659127615

Personally whenever I've ever needed this, I have known the sizes or could easily devise the sizes of each of my rows. The exception I can think of, is a facebook style feed of mixed media where you really don't know what will be there. However those types of lists are almost always lazily loaded and the scrollbar is resized on the fly.

Here we're really talking about displaying massive lists of data, on Desktop, that have some degree of variability to them, often minor. Think a long list with single, double or triple row entries (height => _padding * 2 + rowCount * _rowHeight), or a collection of headers and rows (height => _isHeader? 30 : 60). There is absolutely no reason the list should be doing thousands of measurements in this case, when I can simply give you the value.

The problem isn't the scrollbar, but it's the scrollbar that exposes the inherent flaw in the design, which seems clearly optimized for the limitations of finger-based scrolling.

Probably just the naive developer in me, but I don't understand why we cant use some sort Size getSize() callback, the ScrollController can ask us our size, we can give it width, height or nothing, and then the controller can use that data opportunistically. This seems pretty elegant and powerful to me.

ilijaNL commented 3 years ago

I totally agree with @esDotDev and I also think many scrolling performance problems could be solved if itemExtend could be set per item instead for all the items. However I am not sure if it is sufficient to do that. It means if you have 10000 items it still needs to call the height function 10000 times if a big jump is made. Instead the scrollcontroller should be extended to have a ability to jump x numbers of items, and after the jump the scrolling is resetted. In conclusion the next api's should be added:

When jumpToIndex is used, the list is rerendered from that index (up and down) and the scrollposition is resetted. This requires different layouting mechanism of the viewport however...