-
Notifications
You must be signed in to change notification settings - Fork 26.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Docs] Adding children outside of the cache extent to ListView does not update maxScrollExtent #94925
Comments
Hi @takassh, I can reproduce the issue on stable and the master channel, on dynamically updating the list, the list always scrolls to the previous maxExtent (list before item was added), The result is similar when scroll is scheduled with flutter doctor -v
code sampleimport 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final _scrollController = ScrollController();
int _childCount = 50;
@override
void initState() {
// TODO: implement initState
super.initState();
_scrollController.addListener(() {
print('list scrolled to ${_scrollController.position.pixels}');
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('app bar'),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
print(
' list max extent before = ${_scrollController.position.maxScrollExtent}');
setState(() {
_childCount++;
});
SchedulerBinding.instance?.addPostFrameCallback((_) {
print(
' list max extent afer update = ${_scrollController.position.maxScrollExtent}');
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.easeIn,
);
});
},
child: const Icon(Icons.arrow_downward),
),
body: SafeArea(
child: RefreshIndicator(
onRefresh: () {
return Future.delayed(const Duration(seconds: 1));
},
child: ListView.builder(
controller: _scrollController,
padding: EdgeInsets.zero,
itemCount: _childCount,
itemBuilder: (context, index) {
return ListTile(
title: Center(
child: Text('ListTile $index'),
),
);
},
),
),
),
);
}
}
cc: @Piinks |
AnimateTo
and jumpTo
do not scroll to maxExtent in a dynamic list.
@maheshmnj |
@maheshmnj 2021-12-09.23.30.34.mov |
Hey @takassh I think this is working as expected. When you are tapping your button and adding a child, the maxScrollExtent does not update right away, because it does not build that new child. At the time, it is very far down the list and it is not necessary to build it. It would be expensive to build all of the things in a ListView that the user could not see. As the ListView scrolls, (including while animating), it builds the children just as they are about to appear on screen. This is why you noticed that if you are towards the end of your list, it works as expected. The child is added, and it is within the cache range so it is built in order to be ready to come into view. I found this case on stack overflow: https://stackoverflow.com/questions/57645089/flutter-listview-get-full-size-of-scrollcontroller-after-adding-item-to-list |
AnimateTo
and jumpTo
do not scroll to maxExtent in a dynamic list.
@Piinks The answer of stack overflow works! Please see this sample code and video. Code sampleimport 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
class MyList extends StatefulWidget {
const MyList({Key? key}) : super(key: key);
@override
_MyListState createState() => _MyListState();
}
class _MyListState extends State<MyList> {
final ScrollController _scrollController = ScrollController();
final lastKey = GlobalKey();
final items = List<String>.generate(50, (i) => "Item $i"); // ← changed item length to 50
void add() {
setState(() {
items.add("new Item ${items.length}");
});
SchedulerBinding.instance?.addPostFrameCallback((_) => scrollToEnd());
}
void scrollToEnd() async {
await _scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 200), // ← changed milliseconds to 200
curve: Curves.easeOut
);
Scrollable.ensureVisible(lastKey.currentContext!);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('app bar'),
),
body: ListView.builder(
controller: _scrollController,
itemCount: items.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return ListTile(
title: Text('${items[index]}'),
key: index == items.length - 1 ? lastKey : null,
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
add();
},
child: Icon(Icons.add),
),
);
}
}
2021-12-10.11.16.29.mp4
I think also wrapping Scrollable.ensureVisible(lastKey.currentContext!); to SchedulerBinding.instance?.addPostFrameCallback((_) => Scrollable.ensureVisible(lastKey.currentContext!)); Do you have any advice or idea about this? |
I do not, stackoverflow may be a better place to ask. |
@Piinks |
Since this is working as intended, considering this issue as a documentation request. |
Steps to Reproduce
flutter run
on the code sampleExpected results:
Actual results:
2021-12-09.18.45.56.mov
Code sample
Logs
notice
2021-12-09.19.03.46.mov
2021-12-09.19.03.12.mov
workaround
jumpTo
beforeanimateTo
and wrapanimateTo
inaddPostFrameCallback
The text was updated successfully, but these errors were encountered: