1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.compose.nestedscroll
18 
19 import androidx.compose.foundation.gestures.FlingBehavior
20 import androidx.compose.foundation.gestures.Orientation
21 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
22 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
23 import androidx.compose.ui.util.fastCoerceAtLeast
24 import androidx.compose.ui.util.fastCoerceAtMost
25 
26 /**
27  * A [NestedScrollConnection] that listens for all vertical scroll events and responds in the
28  * following way:
29  * - If you **scroll up**, it **first brings the [height]** back to the [minHeight] and then allows
30  *   scrolling of the children (usually the content).
31  * - If you **scroll down**, it **first allows scrolling of the children** (usually the content) and
32  *   then resets the [height] to [maxHeight].
33  *
34  * This behavior is useful for implementing a
35  * [Large top app bar](https://m3.material.io/components/top-app-bar/specs) effect or something
36  * similar.
37  *
38  * @sample com.android.compose.animation.scene.demo.Shade
39  */
40 fun LargeTopAppBarNestedScrollConnection(
41     height: () -> Float,
42     onHeightChanged: (Float) -> Unit,
43     minHeight: () -> Float,
44     maxHeight: () -> Float,
45     flingBehavior: FlingBehavior,
46 ): PriorityNestedScrollConnection {
47     return PriorityNestedScrollConnection(
48         orientation = Orientation.Vertical,
49         // When swiping up, the LargeTopAppBar will shrink (to [minHeight]) and the content will
50         // expand. Then, you can then scroll down the content.
51         canStartPreScroll = { offsetAvailable, _, _ ->
52             offsetAvailable < 0 && height() > minHeight()
53         },
54         // When swiping down, the content will scroll up until it reaches the top. Then, the
55         // LargeTopAppBar will expand until it reaches its [maxHeight].
56         canStartPostScroll = { offsetAvailable, _, _ ->
57             offsetAvailable > 0 && height() < maxHeight()
58         },
59         canStartPostFling = { false },
60         onStart = {
61             LargeTopAppBarScrollController(
62                 height = height,
63                 maxHeight = maxHeight,
64                 minHeight = minHeight,
65                 onHeightChanged = onHeightChanged,
66                 flingBehavior = flingBehavior,
67             )
68         },
69     )
70 }
71 
72 private class LargeTopAppBarScrollController(
73     val height: () -> Float,
74     val maxHeight: () -> Float,
75     val minHeight: () -> Float,
76     val onHeightChanged: (Float) -> Unit,
77     val flingBehavior: FlingBehavior,
78 ) : ScrollController {
onScrollnull79     override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
80         val currentHeight = height()
81         val amountConsumed =
82             if (deltaScroll > 0) {
83                 val amountLeft = maxHeight() - currentHeight
84                 deltaScroll.fastCoerceAtMost(amountLeft)
85             } else {
86                 val amountLeft = minHeight() - currentHeight
87                 deltaScroll.fastCoerceAtLeast(amountLeft)
88             }
89         onHeightChanged(currentHeight + amountConsumed)
90         return amountConsumed
91     }
92 
onStopnull93     override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
94         return flingToScroll(initialVelocity, flingBehavior)
95     }
96 
onCancelnull97     override fun onCancel() {
98         // do nothing
99     }
100 
canStopOnPreFlingnull101     override fun canStopOnPreFling() = false
102 }
103