In a previous post we saw how to send a RecyclerView‘s scroll events to a compose parent so that we can leverage the top bar’s enter always scroll behavior. The result was good but not great. The top app bar was collapsing and expanding as expected but it was also rendering partially during the transition:
It should either be fully visible or fully hidden.
TopAppBarScrollBehavior
A TopAppBarScrollBehavior defines how an app bar should behave when the content under it is scrolled.
And it does that through its NestedScrollConnection property which provides methods that can be called in both scroll and fling event chains. Their implementation is what changes the bar’s state.
Now, as we saw in the previous post, the way to communicate the events is by using the NestedScrollDispatcher‘s dispatch methods.
Every dispatcher is being bundled with a NestedScrollConnection so, internally, whenever we dispatch an Offset we end up calling the connection’s API thus changing the top bar.
MyEnterAlwaysScrollBehavior
What we want is to have a top bar that will fully collapse when the user scrolls upwards and fully expand when she scrolls downwards. This way we’ll avoid the partial renderings.
The framework does provide this behavior out of the box but in order to leverage it fully you have to be in an all compose project. In our case, since we can’t have this pre/post notion of scroll chain, we are dispatching only pre scroll events. So we have to make it work in a custom component:
This does what we want but it is a bit blunt. The top bar snaps instantly without any animation. Also, notice how it reacts to every tiny scroll movement:
Lets add some animation to the collapse/expand process, a couple of early returns to avoid changing the state if there is no point (already collapsed/expanded) and a small scroll buffer in order to avoid reacting immediately to the user’s gesture:
Now this is better but notice how the bar behaves when we scroll slowly:
This is because the dispatcher keeps sending offsets while the bar’s height is between 0 and the height limit, meaning that the early returns do not apply here. Lets fixit by having an animationInProgress flag:
this does the trick:
Bonus: MyExitUntilCollapsedScrollBehavior
Why stop here and not try to implement the exit until collapsed behavior too?
The idea is to hide the bar when the user scrolls upwards and keep it there until she can no longer scroll downwards. The first part is easy, if the available.y value is equal or greater that 0 we skip everything. The second part is a bit tricky since we don’t have access in this kind of information.
This is where we will leverage the dispatcher adapter from the previous post and enhance the onScrolled method with the check we need:
If the user can no longer scroll downwards we dispatch a post scroll event. Notice that we provide Offeset.Zero in the available for scrolling parameter. This is crucial since we are going to base our implementation on that:
So, we make the assumption that if the available offset is not zero the user can scroll further thus we return. If it is zero we animate the bar’s expansion: