Flutter: how to avoid automatic scrolling in ListView.builder

Riw*_*wen 8 listview flutter

I\'m building a chat application and I\'ve reached the part where I\'m creating the actual chat interface. I decided to use the ListView.builder() constructor, as I\'m working with potentially a great amount of data (messages). The layout, and my code in general, look something like this.

\n

I want to preserve the scrolling position at any time, unless the user explicitly scrolls away. Here comes the first issue: when the software keyboard appears, or the size of the input field changes, I would like the bottom of the messages area to stay visible, and the older (ie. above) messages to get pushed out of the screen. You know, the way it works in Messenger, etc. However, it doesn\'t work like that. The older messages stay visible, and the newer ones get pushed put.

\n

To achieve this, I tried to set the reverse argument to true. While it indeed solved the above issues, it created a newer one. Whenever a new message is added (since it is "reversed", I have to prepend the messages list), the whole messages area appears to "jump". It is, I believe, due to the fact that the since ListView is reversed, the item with index 0 is always the newest message.

\n

However, I don\'t like this behavior. It doesn\'t seem intuitive to the user, who either expects to be scrolled down to the latest message, or be shown a floating UI element indicating that there are new messages.

\n

Question #1: can I somehow disable/change the above behavior for reversed list? I\'m thinking of a callback that calculates the "old" offset and instantly scrolls to that location when a new message is added. Not sure if it\'s possible or if it fits the pattern.

\n

问题#2:我想不使用反向列表,而是添加类似 a 的内容SizeChangedLayoutNotifier,监听 的大小变化ListView,然后滚动到相应的位置。不过,这看起来也很黑客。

\n

一般来说,我想实现类似 Messenger(或者实际上是任何其他类似应用程序)的行为。打开软件键盘应该“上推”消息,并且当新消息到达时,不应该有任何跳转或自动滚动。

\n
\n

更新#1:找到了解决方法,但我宁愿不采用它。每当添加一个项目时,我都会获得position.maxScrollExtent的值ScrollController,然后在更新状态后,我会获得新值。用旧值减去新值,然后减去当前偏移量的差值,最后得到jumpTo这个值。瞧\xc3\xa0, we are at the same message. This has multiple drawbacks, though:

\n
    \n
  • 从这个解决方法的本质来看,它是不稳定的。您必须知道新的最大值。滚动范围将计算差异,因为你无法知道新消息有多长,以及它的气泡的高度是多少
  • \n
  • 比赛条件?您必须等待任意时间(由于缺乏更好的方法,有接受者吗?)才能看到值更新。有用 in the emulator, but will it work on users\' devices?
  • \n
  • 它不是声明性/反应性的。在我当地的例子中,我知道何时添加项目/消息。然而,当使用流时,我需要一个监听器来捕获每次发生的情况,这会很头痛。
  • \n
\n

由于上述原因,我决定不使用此解决方法。

\n
\n

谢谢。

\n

小智 -1

您可以将默认的scrollPhysics更改为此类,以在添加新消息时保留当前位置。用户仍然可以滚动浏览他的消息。

我只是将代码放在这里,供仍在寻找解决方案的人使用。

class PositionRetainedScrollPhysics extends ScrollPhysics {
    final bool? shouldRetain;
    const PositionRetainedScrollPhysics({
       super.parent,
       this.shouldRetain = true,
    });

   @override
   PositionRetainedScrollPhysics applyTo(ScrollPhysics? ancestor){
       return PositionRetainedScrollPhysics(
       parent: buildParent(ancestor),
       shouldRetain: shouldRetain,
       );
     }

   @override
   double adjustPositionForNewDimensions({
       required ScrollMetrics oldPosition,
       required ScrollMetrics newPosition,
       required bool isScrolling,
       required double velocity,
    }) {
       final position = super.adjustPositionForNewDimensions(
          oldPosition: oldPosition,
          newPosition: newPosition,
          isScrolling: isScrolling,
          velocity: velocity,
       );

       // Check if the new position exceeds the scrollable extents
       if (newPosition.maxScrollExtent < oldPosition.maxScrollExtent) {
          return position.clamp(
          oldPosition.minScrollExtent,
          oldPosition.maxScrollExtent,
       );
      }

      final diff = newPosition.maxScrollExtent - oldPosition.maxScrollExtent;

      if (!isScrolling && shouldRetain) {
        return position + diff;
      } else {
        return position;
      }
    }
  }
Run Code Online (Sandbox Code Playgroud)