Skip to content
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

[How to use] Preserve the position to index when load more #115

Closed
dath7 opened this issue Feb 26, 2025 · 10 comments
Closed

[How to use] Preserve the position to index when load more #115

dath7 opened this issue Feb 26, 2025 · 10 comments
Assignees

Comments

@dath7
Copy link

dath7 commented Feb 26, 2025

Platforms

macOS

Description

Hi.
When my chat list at the bottom and scroll down to load lastest data, the scroll bar is always at the bottom. Are there any ways to improve this case ?
Thank u.

Screen.Recording.2025-02-26.at.11.11.12.mov

My code

Based on code in example chat_page:

Widget resultWidget = Listener(
      onPointerSignal: (e)  {
        if ((e is PointerScrollEvent && scrollController.offset == 0 && e.scrollDelta.dy > 0)
          || (e is PointerScrollInertiaCancelEvent && scrollController.offset == 0) ) {
            print("load at bottom");
            _addMessage(RandomTool.genInt(min: 3, max: 5));
          }
        },
      child: ListView.builder(
        physics: chatObserver.isShrinkWrap
            ? const NeverScrollableScrollPhysics()
            : scrollViewPhysics,
        padding: const EdgeInsets.only(
          left: 10,
          right: 10,
          top: 15,
          bottom: 15,
        ),
        shrinkWrap: chatObserver.isShrinkWrap,
        reverse: true,
        controller: scrollController,
        itemBuilder: ((context, index) {
          return ChatItemWidget(
            chatModel: chatModels[index],
            index: index,
            itemCount: chatModels.length,
            onRemove: () {
              chatObserver.standby(isRemove: true);
              setState(() {
                chatModels.removeAt(index);
              });
            },
          );
        }),
        itemCount: chatModels.length,
      ),
    );

Try do it

No response

@LinXunFeng
Copy link
Member

You can do this by setting the fixedPositionOffset to -1.

@dath7
Copy link
Author

dath7 commented Feb 26, 2025

Thank u.
I've tried in two ways but none of those solved my case.

  1. I set and it's worked but that not i want because the ChatObserverClampingScrollPhysics can't jump to bottom anymore when new message arrived.
chatObserver = ChatScrollObserver(observerController)
      ..fixedPositionOffset = -1
      ..toRebuildScrollViewCallback = () {
        setState(() {});
      }
  1. I changed function onPointerSignal but that not worked as expected from fixedPositionOffset = -1
 onPointerSignal: (e)  async {
          if ((e is PointerScrollEvent && scrollController.offset == 0 && e.scrollDelta.dy > 0)
            || (e is PointerScrollInertiaCancelEvent && scrollController.offset == 0) ) {
              print("load at bottom");
              chatObserver.fixedPositionOffset = -1;
              await Future.delayed(const Duration(seconds: 1));
              _addMessage(RandomTool.genInt(min: 3, max: 5));
              chatObserver.fixedPositionOffset = 0;
            }
          },

Could u give me some suggestions?
Thanks a lot.

@LinXunFeng
Copy link
Member

Just make the following adjustments

print("load at bottom");
chatObserver.fixedPositionOffset = -1;
- await Future.delayed(const Duration(seconds: 1));
_addMessage(RandomTool.genInt(min: 3, max: 5));
- chatObserver.fixedPositionOffset = 0;
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+   chatObserver.fixedPositionOffset = 0;
+ });

@dath7
Copy link
Author

dath7 commented Feb 26, 2025

Oh thank u. I forgot that func 😄

@dath7 dath7 closed this as completed Feb 26, 2025
@dath7
Copy link
Author

dath7 commented Feb 27, 2025

Hi @LinXunFeng. Is the max value of changeCount 10 when using chatObserver standby to make that keeping position of message effect ?

@LinXunFeng
Copy link
Member

LinXunFeng commented Feb 27, 2025

The changeCount is the value of the change in the number of messages, and whether keeping position takes effect, in addition to correctly setting the standby parameters, the referenced item also need to be rendered.

Image

@dath7
Copy link
Author

dath7 commented Feb 27, 2025

  • According to the document
    Usage: When you insert a new message, assign the index of the reference message before insertion to [refItemIndex], and assign the index of the reference message after insertion to [refItemIndexAfterUpdate]. Note that they should refer to the index of the same message.

I tried this but not worked 😓 (also mark unread @@) :

 loadMore() async {
    chatObserver.fixedPositionOffset = -1;
    await Future.delayed(const Duration(seconds: 1));

    chatObserver.standby(
      changeCount: 30,
      mode: ChatScrollObserverHandleMode.specified,
      refIndexType: ChatScrollObserverRefIndexType.itemIndex,
      refItemIndex: 1,
      refItemIndexAfterUpdate: 1
    );
    setState(() {
      needIncrementUnreadMsgCount = true;
      for (var i = 0; i < 30; i++) {
        chatModels.insert(0, ChatDataHelper.createChatModel());
      }
    });
    
    WidgetsBinding.instance.addPostFrameCallback((_) {
      chatObserver.fixedPositionOffset = 0;
    });
  }

Video demo:

Screen.Recording.2025-02-27.at.12.06.52.mov

@dath7 dath7 reopened this Feb 27, 2025
@LinXunFeng
Copy link
Member

LinXunFeng commented Feb 28, 2025

Code adjustment

If you want to keep the position when inserting messages normally, you only need to use the following code, which will automatically calculate the index of the referenced item based on the changeCount.

- chatObserver.fixedPositionOffset = -1;
- await Future.delayed(const Duration(seconds: 1));

+ int count = RandomTool.genInt(min: 3, max: 5);
// int count = 30;
chatObserver.standby(
  changeCount: count,
-   mode: ChatScrollObserverHandleMode.specified,
-   refIndexType: ChatScrollObserverRefIndexType.itemIndex,
-   refItemIndex: 1,
-   refItemIndexAfterUpdate: 1,
);
setState(() {
  needIncrementUnreadMsgCount = true;
  for (var i = 0; i < count; i++) {
    chatModels.insert(0, ChatDataHelper.createChatModel());
  }
});

- WidgetsBinding.instance.addPostFrameCallback((_) {
-   chatObserver.fixedPositionOffset = 0;
- });

Note: By default, the mode of standby method is .normal, which will find the index of the first item that has been rendered before inserting the message (starting from the cacheExtent), and use it as the referenced index.

Another problem is that if you insert 30 messages at a time, you need to consider whether the referenced item is still rendered after the ListView is refreshed. In this example, it is not, because cacheExtent is not enough.

About mode: .specified

As for when mode can be set to .specified, it is when you know the absolute index of the referenced item.

For example, if the offset of the ListView is 0 and the index range of the displayed items is [0, 10], you can use .specified. If you insert 5 messages and want to use the item with index 1 as the referenced item, you can set refItemIndex to 1 and refItemIndexAfterUpdate to 1+ 5.

chatObserver.standby(
  changeCount: count,
  mode: ChatScrollObserverHandleMode.specified,
  refIndexType: ChatScrollObserverRefIndexType.itemIndex,
   refItemIndex: 1,
-   refItemIndexAfterUpdate: 1,
+   refItemIndexAfterUpdate: 1 + count,
);

Image

Related document: Specifies the referenced item

@dath7
Copy link
Author

dath7 commented Feb 28, 2025

Thank you!

I had tried that before and set cacheExtent to double.maxFinite, but it didn’t work.

Now, I’ve discovered that the issue was with chatObserver.isShrinkWrap. When I hide it and set shrinkWrap of the list to true, the code didn't work. However, when I unhide it, the code work perfectly. 😄

chatObserver.isShrinkWrap 
    ? const NeverScrollableScrollPhysics() 
    : scrollViewPhysics,

Could you help me to understand what isShrinkWrap in chatObserver does?

Thanks a lot! 💯

@dath7 dath7 closed this as completed Feb 28, 2025
@LinXunFeng
Copy link
Member

The chatObserver.isShrinkWrap corresponds to ListView.shrinkWrap, just to keep the top display when the messages is less than one screen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants