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

[Feature request] Can SliverObserverController be used with ChatScrollObserver? #81

Closed
thaidmfinnick opened this issue May 14, 2024 · 32 comments
Assignees

Comments

@thaidmfinnick
Copy link

thaidmfinnick commented May 14, 2024

Platforms

macOS

Description

Hi, thank for very useful library in Flutter World!

I want to use feature of listView is: fixed to the current chat position for CustomScrollView? Is it possible?

Thank you and look forward to seeing your response!

Why

My final desire feature is to keep scroll when elements of custom scroll view change index, resize. I find this library and it's very clear to me, and I go half of road to complete this feature. I found this wiki here to handle keep scroll with ListObserverController.

@thaidmfinnick
Copy link
Author

thaidmfinnick commented May 15, 2024

Oh I find a way to use ListViewObserver and do not need CustomScrollView, so I can use ChatScrollObserver, the my only problem is can I keep current position of one of my child update height, (Like children of 1 thread have new messages and scroll cannot keep position). Can I do some workaround for this situation to lock this scroll position?

Thank you!

d53fb572ee3f83676fcd3abe748288e4f12a1b51.mov

@LinXunFeng
Copy link
Member

You can complete it by setting the model to ChatScrollObserverHandleMode.specified and specifying the indexes of the reference items before and after inserting the message.

Assume that you are currently changing the content of the item with index 0, then the index of the reference item before and after the change can be specified as 1.

chatObserver.standby(
  mode: ChatScrollObserverHandleMode.specified,
  refItemRelativeIndex: 1,
  refItemRelativeIndexAfterUpdate: 1,
);

final model = chatModels[0];
final newModel = ChatModel(
  isOwn: model.isOwn,
  content: model.content + '123abc',
);
chatModels[0] = newModel;
setState(() {});

See 3.4、Specifies the referenced item for more information.

@thaidmfinnick
Copy link
Author

thaidmfinnick commented May 15, 2024

Thanks for quick response, I have tried but it's not working, to not misunderstand I record a video below:

I have many threads, when 1 thread is update with new message, the scroll position is jump follow, the update messages are children of thread so I guess the index of the reference item is still 0 and not change. I try use refItem: 0 but it stills not working.

I have successfully insert new thread(not messages children of thread) without scroll position but this case isn't.

Screen.Recording.2024-05-15.at.14.14.18.mov

@LinXunFeng
Copy link
Member

Oh, you misunderstood. The reference item should not be the item whose content has changed, but item that do not change.

For example, in thread0, the content of its children has changed, then the reference should be thread1/thread2 etc.

chatObserver.standby(
  mode: ChatScrollObserverHandleMode.specified,
  refItemRelativeIndex: 1, // or 2...
  refItemRelativeIndexAfterUpdate: 1,  // or 2...
);

If you insert a new thread, the original thread0 will become thread1, and the new thread will become thread0. In this scenario, what changes is the new thread, and what remains unchanged is all the original threads. In this case, the reference thread can be the original thread0.

chatObserver.standby(
  mode: ChatScrollObserverHandleMode.specified,
  refItemRelativeIndex: 0, // The index of original thread0 before inserting a new thread.
  refItemRelativeIndexAfterUpdate: 1, // The index of original thread0 after inserting a new thread.
);

It is worth noting that the reference thread/children needs to be rendered before and after the change.

@thaidmfinnick
Copy link
Author

thaidmfinnick commented May 15, 2024

I try and succeedddd, thank you very muchhh! Before closing issue, I have one more question, what actually refItemRelativeIndex use for?

And if 1 item hide 50% height, it's can be treat as display in screen? (I want to make sure to identify correct relative index)

@LinXunFeng
Copy link
Member

what actually refItemRelativeIndex use for?

As shown in the following code, the refItemRelativeIndex is actually a relative index of reference item, used to find the actual index of the item and obtain the layoutOffset before the change.

case ChatScrollObserverHandleMode.specified:
int index = firstItemModel.index + refItemRelativeIndex;
final model = observerController.observeItem(
sliverContext: sliverContext,
index: index,
);
if (model == null) return;
_innerRefItemIndex = index;
_innerRefItemIndexAfterUpdate =
firstItemModel.index + refItemRelativeIndexAfterUpdate;
_innerRefItemLayoutOffset = model.layoutOffset;

And refItemRelativeIndexAfterUpdate is used to find the actual index of the item and obtain the layoutOffset after the change.

/// Observation result of reference subparts after ScrollView children update.
ListViewObserveDisplayingChildModel? observeRefItem() {
return observerController.observeItem(
index: refItemIndexAfterUpdate,
);
}

By subtracting the two, we can calculate how much offset needs to be adjusted after the change.

final model = observer.observeRefItem();
if (model == null) {
_handlePositionCallback(ChatScrollObserverHandlePositionResultModel(
type: ChatScrollObserverHandlePositionType.none,
mode: observer.innerMode,
changeCount: observer.changeCount,
));
return adjustPosition;
}
_handlePositionCallback(ChatScrollObserverHandlePositionResultModel(
type: ChatScrollObserverHandlePositionType.keepPosition,
mode: observer.innerMode,
changeCount: observer.changeCount,
));
final delta = model.layoutOffset - observer.innerRefItemLayoutOffset;
return adjustPosition + delta;

And if 1 item hide 50% height, it's can be treat as display in screen?

Yes, you should be able to understand it well after reading the picture below.

image

@thaidmfinnick
Copy link
Author

thaidmfinnick commented May 15, 2024

Very informative, so what if I choose refItemRelativeIndex outside of screen? I test but cannot see the differences. Also, when I test, the child with (current index = refItemRelativeIndex) will keep scroll when I update other child above? is it correct with logic?

I will close this issue after your answer.

P/s: I really appreciate your unfailing support and I have sent you a small gift as my thankss, have you received it??

@LinXunFeng
Copy link
Member

so what if I choose refItemRelativeIndex outside of screen?

You can use the refItemRelativeIndex outside of screen, as long as it is rendered before and after the change, but I still recommend that you use the index of the unchanged child closest to the changed child as much as possible.

when I test, the child with (current index = refItemRelativeIndex) will keep scroll when I update other child above? is it correct with logic?

I think this may be caused by the current offset of the ListView not exceeding fixedPositionOffset, which you need to check.

See 3.1、Basic usage for more information.

I have sent you a small gift as my thankss, have you received it??

I received your gift, thank you very much. 😁

@thaidmfinnick
Copy link
Author

close as resolved!

@thaidmfinnick
Copy link
Author

thaidmfinnick commented May 15, 2024

@LinXunFeng,
Everything is work as expected, only 1 case I show in video:

my piece code for test:

chatObserver.fixedPositionOffset = -1

chatObserver.standby(
      mode: ChatScrollObserverHandleMode.specified,
      refItemRelativeIndex: 2,
      refItemRelativeIndexAfterUpdate: 2
);

I choose refItemRelativeIndex = 2, it means, scroll will not change at index 2 from viewport, right? So as my understand, when child at index 0, and 1 in viewport update, it will scroll upward instead of scroll downward in video demo. Do you know the reason? Correct me if I'm wrong. Thankss!

Screen.Recording.2024-05-15.at.17.49.55.mov

@LinXunFeng
Copy link
Member

image

Based on your video, I guess the reserve of your ListView should be true, so the relative indexes are also in reverse order.

When the content of child2 changes, the refItemRelativeIndex you specify is also 2. After comparing before and after, the offset of child2 has not changed.

When the content of child1 changes, child2 is affected by child1, the child2's offset increases. However, due to the keep position functionality acts on child2, so what you see is that child1 increases downward.

@thaidmfinnick
Copy link
Author

No, the ListView I setup is not reverse, it's normal listview.

@LinXunFeng
Copy link
Member

LinXunFeng commented May 15, 2024

chatObserver.fixedPositionOffset = -1;

...

final result = await observerController.dispatchOnceObserve(
  isForce: true,
  isDependObserveCallback: false,
);
final displayingFirstChildIndex = result.observeResult?.firstChild?.index ?? 0;
final model = observerController.observeFirstItem();
final cacheFirstChildIndex = model?.index ?? 0;
final delta = displayingFirstChildIndex - cacheFirstChildIndex;

refItemRelativeIndex = 2 + delta;
chatObserver.standby(
  mode: ChatScrollObserverHandleMode.specified,
  refItemRelativeIndex: refItemRelativeIndex,
  refItemRelativeIndexAfterUpdate: refItemRelativeIndex,
);

Adjust the code and try it.

@thaidmfinnick
Copy link
Author

Yeah, it's work, only some situations, although I always listen and lock position to last display relative index, it's still scroll downward like in video, but after I scroll up a little bit, it's correct again. I'm not sure but it means to you in this situation?

Screen.Recording.2024-05-16.at.10.25.53.mov

@LinXunFeng
Copy link
Member

Is your refItemRelativeIndex always 1?

@thaidmfinnick
Copy link
Author

No, I change depends on last display relative index, if my view has [0, 1, 2], I will lock 2, if [0, 1], I will lock 1.

@thaidmfinnick
Copy link
Author

I don't know it's a bug, or not. but i will show in video below:

I have 3 threads in view => I lock position on last relative index: 2
I type on thread with index 1
If first thread have 100% visibility: => I type in thread 1 and thread 1 will jump as I expected
But I scroll down to hide first thread only a little => I type and it's scroll down

My teammates and I are discussing about weird situation. It's about calculation offset, right?

Screen.Recording.2024-05-16.at.10.47.55.mov

@LinXunFeng
Copy link
Member

LinXunFeng commented May 16, 2024

This seem strange. You need to confirm the index of the first thread currently being displayed through the following code.

final result = await observerController.dispatchOnceObserve(
  isForce: true,
  isDependObserveCallback: false,
);
final displayingFirstChildIndex = result.observeResult?.firstChild?.index ?? 0;
debugPrint('displayingFirstChildIndex -- $displayingFirstChildIndex');

Have you set leadingOffset or dynamicLeadingOffset?

@thaidmfinnick
Copy link
Author

thaidmfinnick commented May 16, 2024

Have you set leadingOffset or dynamicLeadingOffset?

No i'm not.

You need to confirm the index of the first thread currently being displayed through the following code.

Oh, do I miss something? displayingFirstChildIndex change? It must the same!

331044631-4a61033d-a67d-43c4-bff9-e076badee76b.mov

@LinXunFeng
Copy link
Member

This may be an illusion. Have you set paddingBottom for thread to implement separator?

To confirm this, you can set a background color for the thread and print its displayPercentage.

final result = await observerController.dispatchOnceObserve(
  isForce: true,
  isDependObserveCallback: false,
);
final firstChild = result.observeResult?.firstChild;
final index = firstChild?.index ?? 0;
final displayPercentage = firstChild?.displayPercentage ?? 0;
debugPrint('firstChild -- $index - $displayPercentage');

@thaidmfinnick
Copy link
Author

This may be an illusion. Have you set paddingBottom for thread to implement separator?

I have marginBottom: 24.

@thaidmfinnick
Copy link
Author

It stills weird, although indexLock = 2, it's still scroll down. As your code you guide me, I print and only 3 items in list. You can see details in video:

Screen.Recording.2024-05-16.at.11.47.00.mov

@thaidmfinnick
Copy link
Author

thaidmfinnick commented May 16, 2024

Do you calculate anything related to first child with 50% percentage? I test when first child has displayPercentage around > 50%, thread 1 will scroll down, else if will scroll up (displayPercentage = 100% it will scroll up too).

I want it scroll up because I have locked scroll position to last display relative index.

@LinXunFeng
Copy link
Member

I’m not sure where the problem lies just by watching the video. You can debug the following code breakpoints in chat_observer_scroll_physics_mixin.dart.

Note that the keep position functionality will only take effect when you go to the third point.

Or can you provide a reproducible demo?

@thaidmfinnick
Copy link
Author

Oke, let me try to reproduce based on example. I will response as soon as possible! Thanks

@thaidmfinnick
Copy link
Author

thaidmfinnick commented May 16, 2024

@LinXunFeng,
I can reproduce my issue in create a demo file in example in your repo, I have forked and you can test. I put my reproducable case in my_test.dart. Do I need create another issue?

Step to reproduce I have put in my_test.dart, and video below:

https://github.com/thaidmfinnick/flutter_scrollview_observer

Screenshot 2024-05-16 at 14 31 26

Screen.Recording.2024-05-16.at.14.36.33.mov

@LinXunFeng
Copy link
Member

Do I need create another issue?

No, I think it's the same issue.

Please adjust the code as follows and try again.

int lastRelativeIndex = 1;
+ int currentFirstChildIndex = 0;
onObserve: (result) {
+  currentFirstChildIndex = result.firstChild?.index ?? 0;
  lastRelativeIndex = result.displayingChildIndexList.length - 1;
},
floatingActionButton: FloatingActionButton(
    onPressed: () {
      print('lastRelativeIndex:$lastRelativeIndex');
      chatScrollObserver.fixedPositionOffset = -1;
+       final model = observerController.observeFirstItem();
+       final cacheFirstChildIndex = model?.index ?? 0;
+       final delta = currentFirstChildIndex - cacheFirstChildIndex;
+       final refItemRelativeIndex = lastRelativeIndex + delta;
      chatScrollObserver.standby(
        mode: ChatScrollObserverHandleMode.specified,
-          refItemRelativeIndex: lastRelativeIndex,
-          refItemRelativeIndexAfterUpdate: lastRelativeIndex,
+         refItemRelativeIndex: refItemRelativeIndex,
+         refItemRelativeIndexAfterUpdate: refItemRelativeIndex,
      );
      setState(() {
        heightForMid += 50;
      });
    },
    child: Icon(Icons.add)),

@thaidmfinnick
Copy link
Author

Oh my god, it's work perfect, you are magician. Why cache is different in this case?

@LinXunFeng
Copy link
Member

LinXunFeng commented May 16, 2024

First of all, I'm sorry. There was a slight mistake in yesterday's picture. I misled you. 😅

image

As shown in the picture above, observeFirstItem will get the first rendered item, even if it is off-screen. Currently, the internal logic of keeping the position functionality also uses observeFirstItem.

Therefore, in order to achieve the relative index logic mentioned yesterday, I used delta to correct.

In the next version, I will add a new parameter to provide this correction logic, so that everyone does not have to correct it by themselves.

@thaidmfinnick
Copy link
Author

thaidmfinnick commented May 16, 2024

Ok, I understand your idea, all my weird questions was answered very carefully by you, and again thank you very much for your unfailing support. I will follow your repos, and use it.

Respect and see you again in other issues!

@LinXunFeng
Copy link
Member

LinXunFeng commented May 19, 2024

Version 1.20.0 has been released.

You can adjust the code without delta as follows.

final refItemIndex = currentThreadIndex + 1;

chatObserver.standby(
  mode: ChatScrollObserverHandleMode.specified,
  refIndexType: ChatScrollObserverRefIndexType.itemIndex,
  refItemIndex: refItemIndex,
  refItemIndexAfterUpdate: refItemIndex,
);

@thaidmfinnick
Copy link
Author

thaidmfinnick commented May 19, 2024

Thanks! I have tested your above code you provide for my project and it works perfect. You divide into 3 specific cases: relativeIndexStartFromCacheExtent, relativeIndexStartFromDisplaying and itemIndex. So clear to me!

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