Open teunklijn opened 8 months ago
Reproducible using the code sample provided above.
It happens in iOS Impeller
@teunklijn did you manage to find a workaround? We are also experiencing this issue and not sure if we should wait for a fix or try to work around it
@teunklijn did you manage to find a workaround? We are also experiencing this issue and not sure if we should wait for a fix or try to work around it
Not really, we've disabled BouncingScrollPhysics
throughout the app until this gets fixed.
return MaterialApp.router(
...
scrollBehavior: ScrollConfiguration.of(context).copyWith(
physics: const ClampingScrollPhysics(),
),
...
);
Alright thanks @teunklijn, in our case that’s sadly not an option since it occurs in the pull to refresh indicator 😅 would look really weird without the bounce
@teunklijn did you manage to find a workaround? We are also experiencing this issue and not sure if we should wait for a fix or try to work around it
@fritsvt I found a work around by wrapping the updated part(Which is Text
in the example) with SizedBox
, Since child size doesn't change, updating it wouldn't trigger SingleChildScrollView
re-layout.
This is very hacky, and will likely break in the future.
@PurplePolyhedron thanks! Unfortunately in our case it wouldn’t always work since the content might get updated after a pull to refresh :(
Edit: just tried and looks like it works but indeed very hacky
The fact they marked this P3 (not important) is really frustrating to see though.
@fritsvt There doesn't seems to be an easy fix to this issue.
Meanwhile, you should be able to use ListView
with shrinkWrap
(which don't have this issue) instead of SingleChildScrollView
in most cases.
Thanks for the suggestion @PurplePolyhedron I’ll give that a try later today. Also hope it will be fixed in the framework soon though!
Overscroll reset happens here:
Added in #136871.
@teunklijn hi, thank you for reporting this.
You can give you Text
widget a tight constraints to avoid to trigger unnecessary relayout for SingleChildScrollView
,
here is a migration for you, holp can help you:
@override
Widget build(BuildContext context) {
return SizedBox(
height: 30,
width: 100,
child: Text('$_count'),
);
}
@teunklijn hi, thank you for reporting this. You can give you
Text
widget a tight constraints to avoid to trigger unnecessary relayout forSingleChildScrollView
, here is a migration for you, holp can help you:@override Widget build(BuildContext context) { return SizedBox( height: 30, width: 100, child: Text('$_count'), ); }
This works as a workaround, but is very error prone. It means that anywhere a SingleChildScrollView
is used, we have to make sure that no content change can trigger a relayout. Since, content change can come from variety of sources (like FutureBuilder rebuilding, any animation that affects size etc.) this increases complexity as it adds another thing we need to keep an eye on.
Changing the performLayout
method inside the SingleChildScrollView
to something like the code below fixes this issue without the need to avoid relayouts.
BoxConstraints? oldConstraints;
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
if (child == null) {
size = constraints.smallest;
} else {
child!.layout(_getInnerConstraints(constraints), parentUsesSize: true);
size = constraints.constrain(child!.size);
}
// restrict resetting an overscroll to zero to only when constraints change
if (offset.hasPixels && constraints != oldConstraints) {
if (offset.pixels > _maxScrollExtent) {
offset.correctBy(_maxScrollExtent - offset.pixels);
} else if (offset.pixels < _minScrollExtent) {
offset.correctBy(_minScrollExtent - offset.pixels);
}
}
oldConstraints = constraints;
offset.applyViewportDimension(_viewportExtent);
offset.applyContentDimensions(_minScrollExtent, _maxScrollExtent);
}
I also tested #105733 with this change and it still works as it should.
@teunklijn hi, thank you for reporting this. You can give you
Text
widget a tight constraints to avoid to trigger unnecessary relayout forSingleChildScrollView
, here is a migration for you, holp can help you:@override Widget build(BuildContext context) { return SizedBox( height: 30, width: 100, child: Text('$_count'), ); }
This works as a workaround, but is very error prone. It means that anywhere a
SingleChildScrollView
is used, we have to make sure that no content change can trigger a relayout. Since, content change can come from variety of sources (like FutureBuilder rebuilding, any animation that affects size etc.) this increases complexity as it adds another thing we need to keep an eye on.Changing the
performLayout
method inside theSingleChildScrollView
to something like the code below fixes this issue without the need to avoid relayouts.BoxConstraints? oldConstraints; @override void performLayout() { final BoxConstraints constraints = this.constraints; if (child == null) { size = constraints.smallest; } else { child!.layout(_getInnerConstraints(constraints), parentUsesSize: true); size = constraints.constrain(child!.size); } // restrict resetting an overscroll to zero to only when constraints change if (offset.hasPixels && constraints != oldConstraints) { if (offset.pixels > _maxScrollExtent) { offset.correctBy(_maxScrollExtent - offset.pixels); } else if (offset.pixels < _minScrollExtent) { offset.correctBy(_minScrollExtent - offset.pixels); } } oldConstraints = constraints; offset.applyViewportDimension(_viewportExtent); offset.applyContentDimensions(_minScrollExtent, _maxScrollExtent); }
I also tested #105733 with this change and it still works as it should.
Thank you very much for you investigation.
The FW will correct the offset during layout phase, This behavior is consistent with other scrollable widget(such as ListView
). If we want to change it, we need to consider all scrollable widgets instead of modifying SingleChildScrollView
alone. I would be happy to continue discussing this issue with you.
This works as a workaround, but is very error prone.
We should aviod trigger relayout during overscrolling, this is unnecessary and inefficient, right?
This works as a workaround, but is very error prone.
We should aviod trigger relayout during overscrolling, this is unnecessary and inefficient, right?
It's true that it's best to avoid relayout if possible, but in some cases a relayout can be necessary or hard to avoid. For example, if an asynchronous data load event coincides with user performing an overscroll. The new data might need a different layout and interrupting the overscroll action will feel jarring to the user.
The FW will correct the offset during layout phase, This behavior is consistent with other scrollable widget(such as
ListView
). If we want to change it, we need to consider all scrollable widgets instead of modifyingSingleChildScrollView
alone. I would be happy to continue discussing this issue with you.
I agree behavior should be kept consistent among all scrollable widgets. Would a modification like this (to correct the offset during layout phase only when constraints change) cause any issues with other scrollable widgets?
Without additional information, we are unfortunately not sure how to resolve this issue. We are therefore reluctantly going to close this bug for now. If you find this problem please file a new issue with the same description, what happens, logs and the output of 'flutter doctor -v'. All system setups can be slightly different so it's always better to open new issues and reference the related ones. Thanks for your contribution.
Is this closed because it was waiting for customer response? I agree that there are workarounds, but they are not always practical. I agree with the comments by @Jure-BB.
I didn't encounter this issue with Flutter 3.22.2, but the same problem is occurring in 3.24. There's a ReorderableList as a child of SingleChildScrollView.
This issue is assigned to @xu-baolin but has had no recent status updates. Please consider unassigning this issue if it is not going to be addressed in the near future. This allows people to have a clearer picture of what work is actually planned. Thanks!
Steps to reproduce
In my prodution app I have a visible stopwatch on a
SingleChildScrollView
. I've included sample code which has the same issue. I think it's related to https://github.com/flutter/flutter/pull/136239, but that one is already closed.Expected results
The scroll position stays the same and there's no visible jitter.
Actual results
The scroll position is reset everytime the text gets updated.
Code sample
Code sample
```dart import 'dart:async'; import 'dart:math'; import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( home: MyHomePage(), ); } } class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: SingleChildScrollView( child: Column( children: [ const _Counter(), for (var i = 0; i < 15; i++) Container( height: 100, color: Color.fromRGBO(Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1), ), ], ), ), ); } } class _Counter extends StatefulWidget { const _Counter(); @override State<_Counter> createState() => __CounterState(); } class __CounterState extends State<_Counter> { int _count = 0; @override void initState() { Timer.periodic(const Duration(milliseconds: 500), (timer) { setState(() { _count++; }); }); super.initState(); } @override Widget build(BuildContext context) { return Text('$_count'); } } ```Screenshots or Video
Screenshots / Video demonstration
**Expected behaviour** https://github.com/flutter/flutter/assets/27430651/a74ba7bb-9598-45f4-88bc-34be11faa897 **Actual behaviour** https://github.com/flutter/flutter/assets/27430651/350a7881-96d3-4eb3-b035-6e0ff84982a6Logs
No response
Flutter Doctor output
Doctor output
```console flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 3.19.3, on macOS 14.3.1 23D60 darwin-arm64, locale nl-NL) [✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) [✓] Xcode - develop for iOS and macOS (Xcode 15.3) [✗] 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 2023.2) [✓] VS Code (version 1.87.0) [✓] Connected device (2 available) ! Error: Browsing on the local area network for iPhone van Govert. Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac. The device must be opted into Developer Mode to connect wirelessly. (code -27) [✓] Network resources ! Doctor found issues in 1 category. ```