11.1 C
London
Friday, February 16, 2024

Understanding Nested Scrolling in Jetpack Compose | by Levi Albuquerque | Android Builders | Feb, 2024


Lists are on the core of most Android apps. Through the years, totally different options had been launched to make sure different UI elements may work together with such lists — as an illustration, how an app bar reacts to record scrolls or how nested lists work together with each other. Have you ever ever encountered a state of affairs the place you’ve got one record inside one other and, by scrolling the internal record to the tip, you’d just like the outer record to proceed the motion? That’s a basic nested scrolling instance!

Nested scrolling is a system the place scrolling elements contained inside one another can talk their scrolling deltas to make them work collectively. As an example, within the View system, NestedScrollingParent and NestedScrollingChild are the constructing blocks for nested scrolling. These constructs are utilized by elements corresponding to NestedScrollView and RecyclerView to allow lots of the nested scrolling use circumstances. Nested scrolling is a key characteristic in lots of UI frameworks, and on this weblog publish we’ll check out how Jetpack Compose handles it.

Let’s take a look at a use case the place the nested scroll system could be useful. On this instance, we’ll create a customized collapsing app bar impact in our app. The collapsing app bar will work together with the record to create the collapsing impact — at any level, if the app bar is expanded, scrolling the record up will trigger it to break down. Equally, if the app bar is collapsed, scrolling the record down will make it develop. Right here’s an instance of what it ought to appear to be:

Let’s assume our app is made up of an app bar and a listing, which is relevant to lots of apps.

Observe: You may obtain related habits by utilizing Materials 3’s TopAppBar scrollBehavior parameter, however we’re rewriting a few of that logic for instance how the nested scrolling system works.

This code renders the next:

By default, there isn’t a communication between our app bar and the record. If we scroll the record, the app bar is static. One different could be to make the app bar be a part of the record itself, however we quickly see that wouldn’t work. As soon as we scrolled the record down, we would wish to scroll all the way in which up once more to see the app bar:

By wanting into this situation, we see that we’d wish to preserve the hierarchical place of the app bar (outdoors of the record). Nevertheless, we additionally need to react to adjustments in scroll within the record — that’s, to make a element react to record scrolls. This can be a trace that the nested scrolling system in Compose could also be a very good resolution for this drawback.

The nested scrolling system is an effective resolution if you’d like coordination between elements when a number of of them is scrollable and so they’re hierarchically linked (within the case above, the app bar and the record share the identical mum or dad). This method hyperlinks scrolling containers and offers a possibility for us to work together with the scrolling deltas which can be being propagated/shared amongst them.

Let’s return a bit and talk about how nested scrolling works generally. The nested scroll cycle is the move of scroll deltas (adjustments) which can be dispatched up and down the hierarchy tree via all elements that may be a part of the nested scrolling system.

Let’s take a listing for instance. When a gesture occasion is detected, even earlier than the record itself can scroll, the deltas will probably be despatched to the nested scroll system. The deltas generated by the occasion will undergo 3 phases: pre-scroll, node consumption, and post-scroll.

  • Within the pre-scroll section, the element that acquired the contact deltas will dispatch these occasions via the hierarchy tree to the topmost mum or dad. Then delta occasions will bubble down, that means that deltas will probably be propagated from the root-most mum or dad down in direction of the kid that began the nested scroll cycle. This offers the nested scroll mother and father alongside this path (composables that use the nestedScroll modifier) the chance to “do one thing” with the delta earlier than the node itself can devour it.

If we return to our diagram, a baby (a listing, as an illustration) scrolling 10 pixels will kickstart the nested scrolling course of. The kid will dispatch the ten pixels up the chain to the root-most mum or dad the place, throughout the pre-scroll section, the mother and father will probably be given the possibility to devour the ten pixels:

On the way in which down in direction of the kid that began the method, any mum or dad might select to devour a part of the ten pixels and the remaining will probably be propagated down the chain. When it reaches the kid, we’ll go to the node consumption section. On this instance, mum or dad 1 selected to devour 5 pixels, so there will probably be 5 pixels left for the subsequent section.

  • Within the node consumption section, the node itself will use no matter delta was not utilized by its mother and father. That is the second that, as an illustration, a listing will really transfer.

Throughout this section, the kid might select to devour all or a part of the remaining scroll. Something left will probably be despatched again as much as undergo the post-scroll section. The kid in our diagram used solely 2 pixels to maneuver, leaving 3 pixels to the subsequent section.

  • Lastly, within the post-scroll section, something that the node itself didn’t devour will probably be despatched up once more to its ancestors in case anybody want to devour it.

The post-scroll section will work in a similar way because the pre-scroll section, the place any of the mother and father might select to devour.

Throughout this section, mum or dad 2 consumed the remaining 3 pixels and reported the remaining 0 pixels down the chain.

Equally, when a drag gesture finishes, the consumer’s intention could also be translated right into a velocity that will probably be used to “fling” the record — that’s, make it scroll utilizing an animation. The fling can also be a part of the nested scroll cycle, and the velocities generated by the drag occasion will undergo related phases: pre-fling, node consumption, and post-fling.

Okay, however how is that this related to our preliminary drawback? Effectively, Compose gives a set of instruments that we will use to affect how these phases work and to work together instantly with them. In our case, if the app bar is presently displaying and we scroll the record up, we’d wish to prioritize scrolling the app bar. However, if we scroll down and the app bar will not be displaying, we’d wish to additionally prioritize scrolling the app bar earlier than scrolling the record itself. That is one other trace that the nested scrolling system could also be a very good resolution: our use case makes us need to do one thing with the scroll deltas even earlier than a listing scrolls (see the hyperlink with the pre-scroll section above).

Let’s take a look at these instruments subsequent.

If we consider the nested scroll cycle as a system appearing on a series of nodes, the nested scroll modifier is our approach of inserting ourselves in adjustments and influencing the information (scroll deltas) which can be propagated on this chain. This modifier could be positioned wherever within the hierarchy, and it communicates with nested scroll modifier cases up the tree so it could possibly share info via this channel. To work together with the knowledge that’s handed via this channel, you should utilize a NestedScrollConnection that can invoke sure callbacks relying on the section of consumption. Let’s take a deeper take a look at the constructing blocks of this modifier:

  • NestedScrollConnection: A connection is a approach to answer the phases of the nested scroll cycle. That is the primary approach you may affect the nested scroll system. It’s composed of 4 callback strategies, every representing one of many phases: pre/post-scroll and pre/post-fling. Every callback additionally provides details about the delta being propagated:

1. out there: The out there delta for that exact section.

2. consumed: The delta consumed within the earlier phases. As an example, onPostScroll has a “consumed” argument, which refers to how a lot was consumed throughout the node consumption section. We might use this worth to be taught, as an illustration, how a lot the originating record has scrolled, since this will probably be invoked after the node consumption section.

3. nested scroll supply: The place that delta originated — Drag (whether it is from a gesture), or Fling (whether it is from a fling animation).

The values returned within the callback are the way in which we’ll inform the system methods to behave. We’ll look extra into this in a bit.

  • NestedScrollDispatcher: A dispatcher is the entity that originates the nested scroll cycle — that’s, utilizing a dispatcher and calling its strategies will basically set off the cycle. As an example, a scrollable container has a built-in dispatcher that takes care of sending deltas captured throughout gestures into the system. For that reason, many of the use circumstances will contain utilizing a connection as a substitute of a dispatcher since we’re reacting to already current deltas relatively than sending new ones.

Now, let’s take into consideration what we all know concerning the propagation order of deltas within the nested scroll system and attempt to apply that info to our use case to see how we may implement the proper collapsing habits for the app bar. Beforehand we discovered that, after a scroll occasion is triggered, even earlier than the record itself can transfer, we will probably be given the possibility to decide in regards to the app bar positioning. This hints that we have to do one thing throughout onPreScroll. Bear in mind, onPreScroll is the section that occurs proper earlier than the record scrolls (NodeConsumption section).

Our preliminary code is a mix of two composables, one for the app bar and one other for the record wrapped round with a Field:

The peak of our app bar is mounted and we will merely offset its place to indicate/cover it. Let’s create a state variable to carry the worth of such offset:

Now, we have to replace the offset primarily based on the scrolling of the record. We’ll set up a nested scroll connection able within the hierarchy the place it will likely be capable of seize deltas coming from the record; on the identical time, it ought to be capable to change the app bar offset. A very good place is the frequent mum or dad of the 2 — the mum or dad is effectively positioned hierarchically to 1) obtain deltas from one element and a pair of) affect the place of the opposite element. We’ll use the connection to affect the onPreScroll section:

Within the onPreScroll callback we’ll obtain the delta from the record within the out there parameter. The return of this callback ought to be no matter we used from out there. Which means if we return Offset.Zero, we didn’t devour something and the record will be capable to use all of it for scrolling. If we return out there, the record gained’t have something left, so it gained’t scroll.

For our use case, if our appBarOffset is something between 0 and the max top of the app bar, we’ll want to present the delta to the app bar (add it to the offset). We will obtain that with a calculation utilizing coerceIn (this limits the values between a minimal and a most). After that, we’ll have to report again to the system what was consumed by the app bar offsetting. Ultimately, our onPreScroll implementation appears to be like like this:

Let’s re-organize our code a bit of bit and summary the state offset and the connection right into a single class:

And now, we will use that class to offset our appBar:

Now, the record will stay static till the app bar is totally collapsed for the reason that app bar offset is consuming the entire delta and there’s nothing left for the record to make use of.

This isn’t precisely what we would like. To repair this, we’ll want to make use of the appBarOffset to additionally replace the house space earlier than our record so when the app bar is absolutely collapsed, the merchandise top will probably be reset. After that, the app bar gained’t devour anything, so the record will be capable to scroll freely.

This logic additionally applies to increasing the app bar. Whereas the app bar is increasing, the record is static, however the invisible merchandise is rising so this provides the phantasm that the record is shifting. As soon as the app bar is absolutely expanded, it gained’t use any extra deltas, and the record will be capable to proceed scrolling.

Within the last end result, the app bar will collapse/develop earlier than the record scrolls as anticipated.

To sum up:

  • We will use the nested scrolling system as a approach of permitting elements somewhere else within the Compose hierarchy to work together with scrolling elements.
  • We will use a NestedScrollConnection to permit adjustments to the propagated deltas inside the nested scrolling cycle.
  • We should always override onPreScroll/onPostScroll strategies to vary scrolling deltas and onPreFling/onPostFling to vary fling velocities.
  • At all times keep in mind to return no matter was consumed in every of the overridden strategies so the nested scrolling cycle can proceed the propagation.

Should you’d wish to be taught extra in regards to the scrolling system, try the official documentation the place there’s a extra technical dialogue in regards to the APIs used right here and how one can interop with View’s nested scrolling system.

Code snippets license: Copyright 2024 Google LLC.

SPDX-License-Identifier: Apache-2.0

Latest news
Related news

LEAVE A REPLY

Please enter your comment!
Please enter your name here