You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Maintain scroll location to prevent content jumping when network images or dynamic network content loads into a listview that has already been laid out #99158
Consider, if there is a ListView with a number of variable height Widgets which further contain network image's that load at their own pace.
When the images load, the height of each individual listview item widget will change (increase) appropriately
If this set of events occurs above the users current scroll position in the listview, then the content will shift / jump. The result is that the user is now looking at content that was formerly above their last viewed position.
This gives for bad user experience.
Is it possible to add a "scroll anchoring" option to listviews (or any scrollable view) that the scroll position should maintain itself relative to the bottom extent of the listview when a layout change occurs above the current position or vice versa when it occurs under neath the current position (as it is currently works). In this way from the user POV, there is not content jumping effect
@mark8044
Thanks for the report. In order to address this properly, can you provide flutter doctor -v, a minimal reproducible code sample that shows the behavior you reported along with actual vs expected result ?
[✓] Flutter (Channel stable, 2.10.3, on macOS 11.6.4 20G417 darwin-x64, locale en-US)
• Flutter version 2.10.3 at /Users//Desktop/flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 7e9793dee1 (5 days ago), 2022-03-02 11:23:12 -0600
• Engine revision bd539267b4
• Dart version 2.16.1
• DevTools version 2.9.2
[✓] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1)
• Android SDK at /Users//Library/Android/sdk
• Platform android-31, build-tools 32.1.0-rc1
• Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7590822)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
• Xcode at /Applications/Xcode.app/Contents/Developer
• CocoaPods version 1.11.2
[✗] Chrome - develop for the web (Cannot find Chrome executable at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome)
! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.
[✓] Android Studio (version 2021.1)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7590822)
[✓] Connected device (1 available)
• iPhone 13 (mobile) • • ios • com.apple.CoreSimulator.SimRuntime.iOS-15-2 (simulator)
[✓] HTTP Host Availability
• All required HTTP hosts are available
class Test extends StatelessWidget {
const Test({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView(
children: [
Text('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sodales mi vel nisi efficitur, sed sollicitudin orci dictum. Phasellus pellentesque ex et libero accumsan porttitor. Ut at enim ex. Nam ultrices risus massa, et tempor lectus finibus eu. Phasellus varius lorem ante, vitae varius nisi elementum nec. Vestibulum blandit ut metus at ultrices. Fusce rhoncus mi enim, a gravida enim egestas ac. Duis nec consequat lectus. Integer vulputate metus ac neque molestie dignissim. Integer lacinia congue nulla molestie elementum. Nulla quis eleifend tortor. In ac nisi ligula. Proin laoreet, metus in ornare tristique, orci lorem aliquet magna, id finibus nisi libero id erat.\n\n'),
Text('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sodales mi vel nisi efficitur, sed sollicitudin orci dictum. Phasellus pellentesque ex et libero accumsan porttitor. Ut at enim ex. Nam ultrices risus massa, et tempor lectus finibus eu. Phasellus varius lorem ante, vitae varius nisi elementum nec. Vestibulum blandit ut metus at ultrices. Fusce rhoncus mi enim, a gravida enim egestas ac. Duis nec consequat lectus. Integer vulputate metus ac neque molestie dignissim. Integer lacinia congue nulla molestie elementum. Nulla quis eleifend tortor. In ac nisi ligula. Proin laoreet, metus in ornare tristique, orci lorem aliquet magna, id finibus nisi libero id erat.\n\n'),
Image.network('https://repository-images.githubusercontent.com/31792824/fb7e5700-6ccc-11e9-83fe-f602e1e1a9f1'),
Text('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sodales mi vel nisi efficitur, sed sollicitudin orci dictum. Phasellus pellentesque ex et libero accumsan porttitor. Ut at enim ex. Nam ultrices risus massa, et tempor lectus finibus eu. Phasellus varius lorem ante, vitae varius nisi elementum nec. Vestibulum blandit ut metus at ultrices. Fusce rhoncus mi enim, a gravida enim egestas ac. Duis nec consequat lectus. Integer vulputate metus ac neque molestie dignissim. Integer lacinia congue nulla molestie elementum. Nulla quis eleifend tortor. In ac nisi ligula. Proin laoreet, metus in ornare tristique, orci lorem aliquet magna, id finibus nisi libero id erat.\n\n'),
Text('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sodales mi vel nisi efficitur, sed sollicitudin orci dictum. Phasellus pellentesque ex et libero accumsan porttitor. Ut at enim ex. Nam ultrices risus massa, et tempor lectus finibus eu. Phasellus varius lorem ante, vitae varius nisi elementum nec. Vestibulum blandit ut metus at ultrices. Fusce rhoncus mi enim, a gravida enim egestas ac. Duis nec consequat lectus. Integer vulputate metus ac neque molestie dignissim. Integer lacinia congue nulla molestie elementum. Nulla quis eleifend tortor. In ac nisi ligula. Proin laoreet, metus in ornare tristique, orci lorem aliquet magna, id finibus nisi libero id erat.\n\n'),
],
);
}
}
Here in this listview, when the lsitview is first shown you see the four blocks of Lorem ipsum text next to each other
When the Network.image loads, then the 3rd&4th paragraphs moves downward (appropriately)
Question here is: Can it be reversed somehow where the 1st&2nd paragraphs actually move upward?
For example, maintain the .extentAfter somehow.
I myself tried to manually adjust the ScrollController.position by doing a ScrollController.jumpTo as soon as the image loads, but unfortunately there is a very slight but minimal glitch perceived by the user as the image pushes down and then the screen jumps down to match its old position
@darshankawar Unfortunately those two refer to to different scenarios to this one
#40340 refers to maintaining a scrollview position between states using a PageStorageKey #63946 refers to scroll position when adding (or removing) listview items
The case I'm describing is a dynamic change in a Container (or otherwise) height which then affects the Listview item height pushing the content to a different location from the current scrollview location, giving an unpleasant experience.
Question here is: Can it be reversed somehow where the 1st&2nd paragraphs actually move upward?
For example, maintain the .extentAfter somehow.
I myself tried to manually adjust the ScrollController.position by doing a ScrollController.jumpTo as soon as the image loads, but unfortunately there is a very slight but minimal glitch perceived by the user as the image pushes down and then the screen jumps down to match its old position
Maybe a ScrollPhysics could address this case?
I'll keep this issue open and label it for further insights on potential solution to address maintaining scroll location.
Hi folks,
To prevent confusion on debugging/addressing this issue, please do not provide feedback for other packages outside of flutter in this issue. :)
I'd like to echo what others have said that this is ideally something that can directly be solved within Flutter's list view instead of requiring lots of manual workaround or external packages.
This is applicable not only for loading images, but also for pagination and real-time content updates, which are very common across many apps.
Hope that the Flutter team could consider to increase the priority for this issue
Reacted by Sophie Silver, lxchx, mark8044, Chit Nan and rlim1987
@santiagos01 and everyone else, make sure to thumb up the original report/first post, as that is how I believe the flutter team decides which issues to elevate or tackle?
To reproduce the issue, I created two minimal examples. In both, the ListView.builder includes the items that change their height at some point in time.
Example 1example_1.movExample 2example_2.movSource Code
I explored the internals of the RenderSliverList class. To work around the jumping behavior mentioned in this issue, I added logic to detect when an item's extent (height) changed compared to a previous layout pass. When a size change is detected, I return a SliverGeometry with a scrollOffsetCorrection value like this:
classRenderSliverListextendsRenderSliverMultiBoxAdaptor {
...
double? _prevSize;
@overridevoidperformLayout() {
...
// Now find the first child that ends after our end.while (endScrollOffset < targetEndScrollOffset) {
if (!advance()) {
reachedEnd =true;
break;
}
if (index ==4) {
finaldouble newSize =paintExtentOf(child!);
if (_prevSize ==null) {
_prevSize = newSize;
} elseif (_prevSize != newSize) {
geometry =SliverGeometry(scrollOffsetCorrection: newSize - _prevSize!);
_prevSize = newSize;
return;
}
}
}
...
}
...
}
This correction signals the RenderViewport to adjust the scroll offset accordingly, and it worked correctly for that particular item (see recording below).
This means that if multiple items resize in a single frame (for example, several images finish loading at once), and the correction takes more than 10 layout cycles to stabilize, the scroll offset might still jump or end up incorrect.
I also reviewed related comments from Flutter contributors that clarify the current architecture and its limitations:
Scroll position is not content aware, so I don't know that it would be possible for the scroll position to accurately and reliably recalculate where the position 'should' be when the content of the list view is dynamically changing. ... You can also try using ListView.itemExtent or ListView.prototypeItem to better control the length of the list and the calculation of the extent.
You can always fix this by caching the size of images yourself and then use SizedBox with cached size when buildings item while scrolling back. The main issue here is that your initially create all your items with very small extent and then they resize after a while when the image is loaded. No ListView implementation can fix this (in theory resizing items in cache area can correct the scroll offset transparently but this will fall apart when you scroll too quickly so the best way is to always create the widget with proper size once you know it).
In summary, the most progress I was able to make to fix the issue at the framework level was through the scroll offset correction implemented in RenderSliverList. While this helps mitigate the issue in some cases, it is not a practical solution due to the RenderViewport’s fixed limit of 10 layout cycles per child.
The current recommendation is to avoid dynamic size changes by estimating item sizes in advance, using fixed-size placeholders, or caching image dimensions.
Activity
darshankawar commentedon Feb 28, 2022
@mark8044
Thanks for the report. In order to address this properly, can you provide
flutter doctor -v, a minimal reproducible code sample that shows the behavior you reported along with actual vs expected result ?mark8044 commentedon Mar 8, 2022
Here in this listview, when the lsitview is first shown you see the four blocks of
Lorem ipsumtext next to each otherQuestion here is: Can it be reversed somehow where the 1st&2nd paragraphs actually move upward?
For example, maintain the
.extentAftersomehow.I myself tried to manually adjust the
ScrollController.positionby doing aScrollController.jumpToas soon as the image loads, but unfortunately there is a very slight but minimal glitch perceived by the user as the image pushes down and then the screen jumps down to match its old positionMaybe a
ScrollPhysicscould address this case?darshankawar commentedon Mar 8, 2022
@mark8044
Please check below issues and see if they help.
#40340
#63946
mark8044 commentedon Mar 10, 2022
@darshankawar Unfortunately those two refer to to different scenarios to this one
#40340 refers to maintaining a scrollview position between states using a PageStorageKey
#63946 refers to scroll position when adding (or removing) listview items
The case I'm describing is a dynamic change in a Container (or otherwise) height which then affects the Listview item height pushing the content to a different location from the current scrollview location, giving an unpleasant experience.
In the HTML/CSS world this effect has sometimes been called Layout Jank (https://css-tricks.com/the-fight-against-layout-jank/) which has been addressed with various forms of scroll anchoring
(https://blog.chromium.org/2017/04/scroll-anchoring-for-web-developers.html)
(https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-anchor/Guide_to_scroll_anchoring)
https://www.youtube.com/watch?time_continue=15&v=-Fr-i4dicCQ&feature=emb_title
darshankawar commentedon Mar 10, 2022
Thanks for the update.
I'll keep this issue open and label it for further insights on potential solution to address maintaining scroll location.
/cc @Piinks
38 remaining items
haroke4 commentedon Dec 11, 2024
hgraceb commentedon Dec 17, 2024
I refactored a package used to maintain the position of a list in my project:
https://github.com/hgraceb/tsukuyomi/blob/31d302588a362f6c7d3cd4ea0f84e1df5d02511d/packages/tsukuyomi_list/lib/src/tsukuyomi_list.dart
Here is an example of its usage in the project:
https://github.com/hgraceb/tsukuyomi/blob/31d302588a362f6c7d3cd4ea0f84e1df5d02511d/lib/pages/debug/list/debug_list_page.dart#L14-L111
Hope this helps.
lxchx commentedon Mar 20, 2025
Piinks commentedon Mar 20, 2025
Hi folks,
To prevent confusion on debugging/addressing this issue, please do not provide feedback for other packages outside of flutter in this issue. :)
santiagos01 commentedon Mar 28, 2025
Bringing the discussion from #165414 here
I'd like to echo what others have said that this is ideally something that can directly be solved within Flutter's list view instead of requiring lots of manual workaround or external packages.
This is applicable not only for loading images, but also for pagination and real-time content updates, which are very common across many apps.
Hope that the Flutter team could consider to increase the priority for this issue
mark8044 commentedon Apr 2, 2025
@santiagos01 and everyone else, make sure to thumb up the original report/first post, as that is how I believe the flutter team decides which issues to elevate or tackle?
ksokolovskyi commentedon May 8, 2025
To reproduce the issue, I created two minimal examples. In both, the
ListView.builderincludes the items that change their height at some point in time.Example 1
example_1.mov
Example 2
example_2.mov
Source Code
I explored the internals of the
RenderSliverListclass. To work around the jumping behavior mentioned in this issue, I added logic to detect when an item's extent (height) changed compared to a previous layout pass. When a size change is detected, I return aSliverGeometrywith ascrollOffsetCorrectionvalue like this:This correction signals the
RenderViewportto adjust the scroll offset accordingly, and it worked correctly for that particular item (see recording below).Scroll Offset Correction
scroll_offset_correction.mov
However, there is an important limitation.
RenderViewportallows a maximum of 10 layout passes with correction per child, as defined here:flutter/packages/flutter/lib/src/rendering/viewport.dart
Line 1538 in 76747c0
This means that if multiple items resize in a single frame (for example, several images finish loading at once), and the correction takes more than 10 layout cycles to stabilize, the scroll offset might still jump or end up incorrect.
I also reviewed related comments from Flutter contributors that clarify the current architecture and its limitations:
From @Piinks #99158 (comment):
From @knopp #134864 (comment):
In summary, the most progress I was able to make to fix the issue at the framework level was through the scroll offset correction implemented in
RenderSliverList. While this helps mitigate the issue in some cases, it is not a practical solution due to theRenderViewport’s fixed limit of 10 layout cycles per child.The current recommendation is to avoid dynamic size changes by estimating item sizes in advance, using fixed-size placeholders, or caching image dimensions.
CHTNANKO commentedon Jun 14, 2025
rlim1987 commentedon Jun 18, 2025
clintdoriot commentedon Apr 6, 2026