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