HorizontalPager continues the fling from horizontal LazyRow. This is apparently Expected behavior bu...
m
HorizontalPager continues the fling from horizontal LazyRow. This is apparently Expected behavior but it differs from traditional android views and also feels really bad. Anyone knows how to work around it? Relevant github issue: https://github.com/google/accompanist/issues/347#event-4607690257
c
You can add your own
nestedScroll()
modifier on the
LazyRow
and set the
dispatcher
to an empty/no-op dispatcher
🙏 1
@matvei should we add a way to disable the automatic nested scroll behaviour on
scrollable()
?
2
m
Hmmm, I'm not sure we should. scrollable should support nested scroll as a middle node (current default), as a parent only (view pager case) or as a child only (not sure I have use cases in mind). We could allow developers to tweak dispatch/connection in the scrollable, however, it is already doable and in a more elegant way imo: just use another nested scroll node to specify how to communicate with parent or child.
If we don't want to allow ViewPager to react on a fling from children - we can insert one more nested scroll node in the vp implementation, e.g
Modifier.scrollable(viewpagerState).nestedScroll(ConsumeAllPostFlingNestedScrollConnection)
. This approach is more scalable and flexible, although not that elegant on a first glance
m
@cb I've returned to this issue but can't quite get how to provide a no-op dispatcher. NestedScrollDispatcher is a final class and if I provide null, it's just replaced with the default impl. Is it possible or the only way is to approach this from the Pager side and use a custom Connection to stop accepting nested scrolls?
m
You only need a no-op connection, not a dispatcher. Dispatcher is needed when you need to dispatch scroll events you are receiving from the gesture system. Please, refer to the Modifier.nestedScroll documentation, I hope that helps
m
@matvei I did this on the child LazyRow
Copy code
private class NoOpNestedScrollConnection : NestedScrollConnection {
    override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource) = Offset(available.x, 0f)
}
And it kinda works, but I feel like I should do it on the Pager side, but cannot quite get it right.
m
you can do
Copy code
HorizontalPager(modifier = Modifier.nestedScroll(remember { NoOpNestedScroll() } )) {}
And that's should do the trick. This is as close as you get it to the pager
m
yes, this is what I tried, but I couldn't get the
NoOpNestedScroll
right. Didn't know which method should return zero offset and which should consume. (I assume that
NoOpNestedScroll
is not something that exists in compose lib, right?)
it was either working as before or just blocking the scrolling entirely
m
According to the documentation, I think if you will consume all available offset on the onPostScroll and onPostFling - that should block the parent from post processing. There's no way to block pre passes though, as the most outer parent gets the pre events first.
m
I think I tried that, but maybe messed up something. Will check in a few minutes and let you know.
Copy code
class NoOpNestedScrollConnection : NestedScrollConnection {
    override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource) = Offset(available.x, 0f)
    override suspend fun onPostFling(consumed: Velocity, available: Velocity) = Velocity(available.x, 0f)
}
If I add this to the HorizontalPager, nothing changes, it still behaves like before (so a nested Row overscroll makes it scroll to the side). However if I add the same thing to the nested Row, it behaves as expected - the overscroll is not proagated to the HorizontalPager (actually onPostScroll is enough, onPostFling not required, even for flings here).
m
Oh, sorry, i probably misunderstood the overall problem. If you have LezyRow nested inside the HorizontalPager then the NoOp connection should come after the HorizontalPager, e.g. in the first child or any child actually before the LazyRow. Modifier.nestedScroll works in the same way any other modifier does - order matters. The nested events are propagated by, so if you want your child to not to affect the parent, you have to consume the delta somewhere between the parent and a child. So it seems like you are doing everything right.
m
Sorry, I might have not been clear enough about the issue 🙂 However I'm still having a doubt about the whole thing. I would assume, that my HorizontalPager should not have to care about its children and would be able to handle it's own behavior as lomg as its children DO DISPATCH the overscroll. The point is that ultimately LazyRow is doing the right thing - it dispatches the overscroll like a good nested scroll citizen. It should rather be up to HorizontalPager to either accept it and continue the scroll or to ignore it. And that is something I could not achieve.
m
Yeah I understand the problem, you have a very good intuition on this one! Let's think about the whole HozirontalPager behaviour then. Is there a case where you actually want children of the pager to affect the pager itself? I have no doubts the pager should dispatch the scroll events to the parent, but I'm not sure it makes sense at all for pager to scroll when the child hits the bound. @cb do you have an opinion on this one? We need to investigate how the ViewPager2 handles this in the View world.
m
The original pager did not scroll on children overscroll, hence the original issue I opened on github. Personally I never had issue with that behavior - seemed very natural. But I'm not sure about the internals of either Pager - e.g. I'm not sure if the old Pager didn;t visually react on the children overscroll but maybe it silently passed it up the hierarchy? That would mean 3 scrollable-in-the-same-direction containers, which seems a bit weird but maybe there's a usecase.
c
The original VP only did that because it was written at a time when nested scrolling didn't really exist (in terms of APIs). My opinion is that nested scrolling containers should always 'unwrap' drags unless there's an obvious reason not to. Flings are a little bit more open to opinion, but I can see why you wouldn't want it to do so
m
It's true, it's the fling which makes it a bit annoying ux-wise, not the scroll. I'm not even saying it should be the default behavior, but right now I can't find a method to suppress it at all. Design-wise I thought about two solutions. Some additional parameter in Pager, or a ready-to-use ScrollConnection.
j
sorry to jump in and bring this issue from the grave. I’m facing this issue with a
LazyRow
and a
ViewPager2
. any suggestion on how to workaround it?
m
Interesting. There should be no such issue when
LazyRow
is in the view-based container like
RecyclerView
or
ViewPager2
as we don't have nested scroll interoperability yet. What's the issue you are observing?
j
two issues: • if i fling fast in the
LazyRow
it swipes the pager • when i reach the end of the
LazyRow
the fling/swipe don’t move the pager
@matvei can you give any insight on how can I workaround it? there are also other guys around the globe with the same issue (eg: https://stackoverflow.com/questions/68038792/scroll-issues-with-lazyrow-inside-traditional-android-viewpager)
m
if i fling fast in the 
LazyRow
 it swipes the pager
Do you have a sample code that reproduces this problem with original ViewPager2? It should only happen when you have a HorizontalPager that is compose's pager and NOT with interop. For the second one, we have a bug filed (https://issuetracker.google.com/issues/192006539), but no ETA that I can provide yet.
j
I will create a sample code with it and will share. the main problem is having the viewpager2 (horinztal) with a lazyrow has its children. sometimes the viewpager2 got the fling/swipe and not the lazyRow.
m
Thanks, that would help. Please run the sample through the latest compose beta as we've fixed a few gesture interop issues there
a
Any idea how to do the same but with dpad key events? For example, having a horizontally scrollable
Row
inside a
HorizontalPager
scrolls the Pager after reaching the end of visible row items. Using the no-op
NestedScreenConnection
doesn’t seem to work in this case.
a
Hi @matvei, I'm facing the similar problem. I have a
HorizontalPager
inside
ViewPager2
. When I fling fast over
HorizontlPager
, the
ViewPager2
takes the event and moves to next page. How to fix this? @jzeferino were you able to fix that fling issue with lazyrow and viewpager2?
1132 Views