Join the Compose Chinese technology community to get more excellent technical tutorials!

There is currently a Chinese manual project for Jetpack Compose, which aims to help developers better understand and master the Compose framework. This article was written by myself. At present, it has been published in this manual. Welcome to refer to it.

Slide refresh effect display

Gestures that involve nested swipes, such as swipe refresh, need to use the nestedScroll modifier. So, let’s talk about what the nestedScroll modifier is and how to use it.

NestedScroll modifier

The nestedScroll modifier is mainly used to handle nestedScroll scenarios, making it possible for the parent layout to hijack the consumption of child layout swipe gestures.

The nestedScroll parameter list has a mandatory parameter connection and an optional parameter dispatcher

Connection: Nested core logic for sliding gesture processing. Internal callbacks can pre-consume some or all of the gesture offsets before the child layout gets the sliding event, or they can get the gesture offsets left after the child layout consumes them.

Dispatcher: a dispatcher that contains a NestedScrollConnection for the parent layout and can call the Dispatch * method to notify the parent layout of a slide

fun Modifier.nestedScroll(
    connection: NestedScrollConnection,
    dispatcher: NestedScrollDispatcher? = null
)
Copy the code

NestedScrollConnection

NestedScrollConnection provides four callback methods.

interface NestedScrollConnection {
    fun onPreScroll(available: Offset, source: NestedScrollSource): Offset = Offset.Zero

    fun onPostScroll(
        consumed: Offset,
        available: Offset,
        source: NestedScrollSource
    ): Offset = Offset.Zero

    suspend fun onPreFling(available: Velocity): Velocity = Velocity.Zero

    suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
        return Velocity.Zero
    }
}
Copy the code

onPreScroll

Method description: hijack the slide event in advance, and then deliver it to the sub-layout after consumption.

Parameter list:

  • Available: Indicates the offset of the current available slide event
  • Source: Type of slide event

Return value: the sliding event Offset of the current component consumption, Offset.Zero if you do not want to consume


onPostScroll

Method description: Gets the sliding event after the child layout is processed

Parameter list:

  • Consumed: All sliding event offsets previously consumed
  • Available: Indicates the offset of the sliding event that is currently available
  • Source: Type of slide event

Return value: the sliding event Offset currently consumed by the component. Offset.Zero can be returned if you do not want to consume, and the remaining Offset will continue to be processed by the parent of the current layout


onPreFling

Get the speed at which the Fling starts.

Parameter list:

  • The available:FlingThe initial velocity

Return value: the speed at which the current component consumes, or velocity.zero if you do not want to consume


onPostFling

Method Description: Obtain the speed information at the end of the Fling.

Parameter list:

  • Consumed: All speeds previously consumed

  • Available: indicates the current available speed

Return value: the speed at which the current component consumes. If you don’t want to consume, return velocity.zero, and the remaining speed will continue to be handled by the parent of the current layout.

It is not a Fling. OnPreFling is not a Fling where your finger is not in the air but in the air. It is not a Fling where your finger is not in the air but in the air.

Slide refresh

Gestures that involve nested swipes, such as swipe refresh, can be done using the nestedScroll modifier.

The sample is introduced

In this example, there is loading animation and list data. As we slide our finger down, the loading animation gradually appears if there is no data at the top of the list. In contrast, when we swipe up, if the loaded animation is still there, the loaded animation gradually disappears up, and the list is not swiped down until the loaded animation is completely gone.

Design and Implementation scheme

In order to realize this sliding refresh requirement, we can design the following scheme. We first need to manage the loading animation and the list data in a single parent layout.

  1. When we slide down, we want the sliding gesture to be processed first by the list in the child layout. If the list has slid to the top, the sliding gesture event has not been consumed at this point, and then by the parent layout for consumption. The parent layout can consume the remaining sliding gesture events from the consumption list (adding offsets for loading animations).

  2. When we swipe up, we want the swipe gesture to be consumed by the parent layout first (to reduce the offset for the loading animation) and not if the loading animation itself is still not present. The remaining swiping gestures are then handed over to the sub-layout list for consumption.

NestedScrollConnection implementation

The most important thing to use the nestedScroll modifier is to customize the implementation of NestedScrollConnection according to your own business scenario. Next, we will analyze how to implement the NestedScrollConnection one by one.

Implement onPostScroll

Like the implementation we designed earlier, when we slide down, we want the sliding gesture to be processed first by the list in the child layout. If the list has slid to the top, the sliding gesture event is not consumed at this point, and then by the parent layout for consumption. The onPostScroll callback timing is in line with our needs.

Y > 0 to determine whether the slide event is a drag event, and if it is a slide gesture, if all is ok, inform the loading animation to increase the offset. The return value Offset(x = 0f, y = available.y) means that all remaining offsets are consumed and no longer propagated to the outer parent layout.

override fun onPostScroll(
    consumed: Offset,
    available: Offset,
    source: NestedScrollSource
): Offset {
    if (source == NestedScrollSource.Drag && available.y > 0) {
        state.updateOffsetDelta(available.y)
        return Offset(x = 0f, y = available.y)
    } else {
        return Offset.Zero
    }
}
Copy the code

Implement onPreScroll

In contrast, we want to slide and retract the loaded animation, and when we swipe up, we want the sliding gesture to be consumed by the parent layout first (to reduce the offset for the loaded animation), and not if the loaded animation itself is still not present. The remaining swiping gestures are then handed over to the sub-layout list for consumption. The onPreScroll callback timing fits this requirement.

We first need to determine whether the slide event is a drag event, and check whether it is a slide up gesture through available.y < 0. The loading animation itself may not appear at this point, so additional judgment is required. Zero is not consumed if it is not. If it is, Offset(x = 0f, y = available.y) is returned for consumption.

override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
    if (source == NestedScrollSource.Drag && available.y < 0) {
        state.updateOffsetDelta(available.y)
        return if (state.isSwipeInProgress) Offset(x = 0f, y = available.y) else Offset.Zero
    } else {
        return Offset.Zero
    }
}
Copy the code

Implement onPreFling

Next, we need a grip when we let go. If you pull over the general height of the loading animation, load, otherwise shrink back to the initial state. Earlier I mentioned that the onPreFling falls back when it lets go, which is exactly what we’re doing here.

Both onPreFling and onPostFling are called back, even if they are slow or still.

Here we only need the attraction effect, not the consumption speed, so return velocity.zero

override suspend fun onPreFling(available: Velocity): Velocity {
    if (state.indicatorOffset > height / 2) {
        state.animateToOffset(height)
        state.isRefreshing = true
    } else {
        state.animateToOffset(0.dp)
    }
    return Velocity.Zero
}
Copy the code

Implement onPreFling

Since our slide refresh gesture processing does not involve onPreFling callback timing, no additional implementation is required.

The sample source code

The full source code for this example is open source on my Github Repo, so feel free to read it and submit any feedback.