虽然所有答案都能产生期望的效果,但是我们应该做一些改进。
首先,在大多数情况下(关于自动滚动),使用postFrameCallbacks是无用的,因为一些内容可能会在ScrollController附加(通过attach
方法)之后渲染,控制器将滚动到它所知道的最后位置,而该位置可能不是视图中的最新位置。
使用reverse:true
是一个不错的技巧来“尾随”内容,但是物理上会被反转,因此当您尝试手动移动滚动条时,必须将其移动到相反的方向->不好的用户体验。
在设计图形界面时,使用计时器是一种非常糟糕的做法->计时器在更新/生成图形造成干扰。
无论怎样,正确完成任务的方法是使用jumpTo
方法和hasClients方法作为保护条件。
是否有任何ScrollPosition对象使用attach方法附加到ScrollController。
如果这是false,则不能调用与ScrollPosition交互的成员,例如position、offset、animateTo和jumpTo。
在代码中,可以简单地这样做:
if (_scrollController.hasClients) {
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
}
无论如何,这段代码还不够好,即使可滚动区域没有到屏幕底部,该方法也会被触发,因此,如果您手动移动滚动条,该方法将被触发并执行自动滚动。
我们可以做得更好,借助一个监听器和一对 bool 变量就可以了。
我正在使用这个技巧来在 SelectableText 中可视化一个大小为 100000 的 CircularBuffer 的值,内容保持正确更新,自动滚动非常平滑,即使是非常非常长的内容也没有性能问题。也许正如其他答案中有人所说的那样,animateTo
方法可能会更流畅、更可定制,所以请随意尝试。
ScrollController _scrollController = new ScrollController();
bool _firstAutoscrollExecuted = false;
bool _shouldAutoscroll = false;
void _scrollToBottom() {
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
}
void _scrollListener() {
_firstAutoscrollExecuted = true;
if (_scrollController.hasClients && _scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
_shouldAutoscroll = true;
} else {
_shouldAutoscroll = false;
}
}
@override
void initState() {
super.initState();
_scrollController.addListener(_scrollListener);
}
@override
void dispose() {
_scrollController.removeListener(_scrollListener);
super.dispose();
}
- 根据您的逻辑和需求,在
setState
中触发_scrollToBottom
:
setState(() {
if (_scrollController.hasClients && _shouldAutoscroll) {
_scrollToBottom();
}
if (!_firstAutoscrollExecuted && _scrollController.hasClients) {
_scrollToBottom();
}
});
解释
- 我们创建了一个简单的方法:
_scrollToBottom()
以避免代码重复;
- 我们创建了一个
_scrollListener()
并将其附加到_scrollController
中的 initState
-> 在滚动条移动后第一次触发。在此侦听器中,我们更新布尔值_shouldAutoscroll
的值,以了解滚动条是否位于屏幕底部。
- 我们在
dispose
中删除侦听器,以确保在小部件处置后不做无用的操作。
- 在我们的
setState
中,当我们确定_scrollController
已附加且该控制器位于底部(检查shouldAutoscroll
的值)时,我们可以调用_scrollToBottom()
。
同时,仅对第一次执行,我们通过_firstAutoscrollExecuted
的值来强制短路_scrollToBottom()
。