├── .gitignore ├── .idea ├── .gitignore └── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── me │ │ └── onebone │ │ └── toolbar │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── me │ │ │ └── onebone │ │ │ └── toolbar │ │ │ ├── MainActivity.kt │ │ │ ├── ParallaxActivity.kt │ │ │ └── ui │ │ │ └── theme │ │ │ ├── Color.kt │ │ │ ├── Shape.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── android.png │ │ └── ic_launcher_background.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── me │ └── onebone │ └── toolbar │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle ├── publish.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img ├── enter-always-collapsed.gif ├── enter-always.gif └── exit-until-collapsed.gif ├── lib ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── me │ │ └── onebone │ │ └── toolbar │ │ ├── CollapsingToolbarTest.kt │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── me │ │ └── onebone │ │ └── toolbar │ │ ├── Annotations.kt │ │ ├── AppBarContainer.kt │ │ ├── CollapsingToolbar.kt │ │ ├── CollapsingToolbarScaffold.kt │ │ ├── FabPlacement.kt │ │ ├── FabPosition.kt │ │ ├── ScrollStrategy.kt │ │ └── ToolbarWithFabScaffold.kt │ └── test │ └── java │ └── me │ └── onebone │ └── toolbar │ └── ExampleUnitTest.kt └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/android,androidstudio 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=android,androidstudio 3 | 4 | ### Android ### 5 | # Built application files 6 | *.apk 7 | *.aar 8 | *.ap_ 9 | *.aab 10 | 11 | # Files for the ART/Dalvik VM 12 | *.dex 13 | 14 | # Java class files 15 | *.class 16 | 17 | # Generated files 18 | bin/ 19 | gen/ 20 | out/ 21 | # Uncomment the following line in case you need and you don't have the release build type files in your app 22 | # release/ 23 | 24 | # Gradle files 25 | .gradle/ 26 | build/ 27 | 28 | # Local configuration file (sdk path, etc) 29 | local.properties 30 | 31 | # Proguard folder generated by Eclipse 32 | proguard/ 33 | 34 | # Log Files 35 | *.log 36 | 37 | # Android Studio Navigation editor temp files 38 | .navigation/ 39 | 40 | # Android Studio captures folder 41 | captures/ 42 | 43 | # IntelliJ 44 | *.iml 45 | .idea/workspace.xml 46 | .idea/tasks.xml 47 | .idea/gradle.xml 48 | .idea/assetWizardSettings.xml 49 | .idea/dictionaries 50 | .idea/libraries 51 | # Android Studio 3 in .gitignore file. 52 | .idea/caches 53 | .idea/modules.xml 54 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 55 | .idea/navEditor.xml 56 | 57 | .idea/deploymentTargetDropDown.xml 58 | 59 | # Keystore files 60 | # Uncomment the following lines if you do not want to check your keystore files in. 61 | #*.jks 62 | #*.keystore 63 | 64 | # External native build folder generated in Android Studio 2.2 and later 65 | .externalNativeBuild 66 | .cxx/ 67 | 68 | # Google Services (e.g. APIs or Firebase) 69 | # google-services.json 70 | 71 | # Freeline 72 | freeline.py 73 | freeline/ 74 | freeline_project_description.json 75 | 76 | # fastlane 77 | fastlane/report.xml 78 | fastlane/Preview.html 79 | fastlane/screenshots 80 | fastlane/test_output 81 | fastlane/readme.md 82 | 83 | # Version control 84 | vcs.xml 85 | 86 | # lint 87 | lint/intermediates/ 88 | lint/generated/ 89 | lint/outputs/ 90 | lint/tmp/ 91 | # lint/reports/ 92 | 93 | ### Android Patch ### 94 | gen-external-apklibs 95 | output.json 96 | 97 | # Replacement of .externalNativeBuild directories introduced 98 | # with Android Studio 3.5. 99 | 100 | ### AndroidStudio ### 101 | # Covers files to be ignored for android development using Android Studio. 102 | 103 | # Built application files 104 | 105 | # Files for the ART/Dalvik VM 106 | 107 | # Java class files 108 | 109 | # Generated files 110 | 111 | # Gradle files 112 | .gradle 113 | 114 | # Signing files 115 | .signing/ 116 | 117 | # Local configuration file (sdk path, etc) 118 | 119 | # Proguard folder generated by Eclipse 120 | 121 | # Log Files 122 | 123 | # Android Studio 124 | /*/build/ 125 | /*/local.properties 126 | /*/out 127 | /*/*/build 128 | /*/*/production 129 | *.ipr 130 | *~ 131 | *.swp 132 | 133 | # Keystore files 134 | *.jks 135 | *.keystore 136 | 137 | # Google Services (e.g. APIs or Firebase) 138 | # google-services.json 139 | 140 | # Android Patch 141 | 142 | # External native build folder generated in Android Studio 2.2 and later 143 | 144 | # NDK 145 | obj/ 146 | 147 | # IntelliJ IDEA 148 | *.iws 149 | /out/ 150 | 151 | # User-specific configurations 152 | .idea/caches/ 153 | .idea/libraries/ 154 | .idea/shelf/ 155 | .idea/.name 156 | .idea/compiler.xml 157 | .idea/copyright/profiles_settings.xml 158 | .idea/encodings.xml 159 | .idea/misc.xml 160 | .idea/scopes/scope_settings.xml 161 | .idea/vcs.xml 162 | .idea/jsLibraryMappings.xml 163 | .idea/datasources.xml 164 | .idea/dataSources.ids 165 | .idea/sqlDataSources.xml 166 | .idea/dynamic.xml 167 | .idea/uiDesigner.xml 168 | .idea/jarRepositories.xml 169 | 170 | # OS-specific files 171 | .DS_Store 172 | .DS_Store? 173 | ._* 174 | .Spotlight-V100 175 | .Trashes 176 | ehthumbs.db 177 | Thumbs.db 178 | 179 | # Legacy Eclipse project files 180 | .classpath 181 | .project 182 | .cproject 183 | .settings/ 184 | 185 | # Mobile Tools for Java (J2ME) 186 | .mtj.tmp/ 187 | 188 | # Package Files # 189 | *.war 190 | *.ear 191 | 192 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) 193 | hs_err_pid* 194 | 195 | ## Plugin-specific files: 196 | 197 | # mpeltonen/sbt-idea plugin 198 | .idea_modules/ 199 | 200 | # JIRA plugin 201 | atlassian-ide-plugin.xml 202 | 203 | # Mongo Explorer plugin 204 | .idea/mongoSettings.xml 205 | 206 | # Crashlytics plugin (for Android Studio and IntelliJ) 207 | com_crashlytics_export_strings.xml 208 | crashlytics.properties 209 | crashlytics-build.properties 210 | fabric.properties 211 | 212 | /publish.properties 213 | 214 | ### AndroidStudio Patch ### 215 | 216 | !/gradle/wrapper/gradle-wrapper.jar 217 | 218 | # End of https://www.toptal.com/developers/gitignore/api/android,androidstudio -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 144 | 145 | 151 | 152 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 onebone 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 19 | OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # compose-collapsing-toolbar 2 | A simple implementation of [CollapsingToolbarLayout](https://developer.android.com/reference/com/google/android/material/appbar/CollapsingToolbarLayout) for Jetpack Compose 3 | 4 | ## Installation 5 | You should add `mavenCentral()` repository before installation. Then add the following line to the `dependencies` block in your app level build.gradle: 6 | 7 | ```gradle 8 | implementation "me.onebone:toolbar-compose:2.3.5" 9 | ``` 10 | or build.gradle.kts: 11 | ```kotlin 12 | implementation("me.onebone:toolbar-compose:2.3.5") 13 | ``` 14 | 15 | ## Example 16 | An example can be found [here](app/src/main). 17 | 18 | ## Usage 19 | ### Using CollapsingToolbarScaffold 20 | `CollapsingToolbarScaffold` is a container to help you place composables and move them as a user dispatches scroll. It provides two holes where you can place you components. 21 | To use `CollapsingToolbarScaffold` you will need `CollapsingToolbarScaffoldState` which could be retrieved using `rememberCollapsingToolbarScaffoldState()`. 22 | ```kotlin 23 | CollapsingToolbarScaffold( 24 | state = rememberCollapsingToolbarScaffoldState(), // provide the state of the scaffold 25 | toolbar = { 26 | // contents of toolbar go here... 27 | } 28 | ) { 29 | // main contents go here... 30 | } 31 | ``` 32 | 33 | The toolbar will collapse until it gets as small as the smallest child, and will expand as large as the largest child. 34 | 35 | Also note that the content should be scrollable for the `CollapsingToolbarScaffold` to consume nested scroll. For `LazyColumn`, you don't have to care of anything because it is scrollable by default. Column, however, is not scrollable by default so you can provide `Modifier.verticalScroll()` to make a content dispatch nested scroll. 36 | 37 | ```kotlin 38 | CollapsingToolbarScaffold( 39 | state = rememberCollapsingToolbarScaffoldState(), // provide the state of the scaffold 40 | toolbar = { 41 | // contents of toolbar go here... 42 | } 43 | ) { 44 | Column( 45 | modifier = Modifier 46 | .verticalScroll(rememberScrollState()) // main content should be scrollable for CollapsingToolbarScaffold to consume nested scroll 47 | ) { 48 | // ... 49 | } 50 | } 51 | ``` 52 | 53 | By default, `CollapsingToolbar` clips content to its bounds. In order to disable it, set `toolbarClipToBounds = false` in `CollapsingToolbarScaffold`. 54 | 55 | ### CollapsingToolbarScaffoldState 56 | `CollapsingToolbarScaffoldState` is a holder of the scaffold state, such as the value of y offset and how much the toolbar has expanded. The field is public so you may use it as you need. 57 | Note that the `CollapsingToolbarScaffoldState` is stable, which means that a change on a value of the state triggers a recomposition. 58 | ```kotlin 59 | val state = rememberCollapsingToolbarScaffoldState() 60 | val offsetY = state.offsetY // y offset of the layout 61 | val progress = state.toolbarState.progress // how much the toolbar is expanded (0: collapsed, 1: expanded) 62 | 63 | Text( 64 | text = "Hello World", 65 | textSize = (18 + (30 - 18) * progress).sp // text size depending on the progress 66 | // recomposed when the value of the progress is changed 67 | ) 68 | ``` 69 | 70 | ## parallax, pin, road 71 | You can tell children of CollapsingToolbar how to deal with a collapse/expansion. This works almost the same way to the `collapseMode` in the `CollapsingToolbarLayout` except for the `road` modifier. 72 | 73 | ```kotlin 74 | CollapsingToolbar(/* ... */) { 75 | Image( 76 | modifier = Modifier.parallax(ratio = 0.2f) // parallax, pin, road are available 77 | ) 78 | } 79 | ``` 80 | 81 | ### road modifier 82 | The `road()` modifier allows you to place a child relatively to the toolbar. It receives two arguments: `whenCollapsed` and `whenExpanded`. As the name suggests, these describe how to place a child when the toolbar is collapsed or expanded, respectively. 83 | This can be used to display a title text on the toolbar which is moving as the scroll is fed. 84 | ```kotlin 85 | CollapsingToolbarScaffold( 86 | toolbar = { 87 | Text( 88 | text = "Title", 89 | modifier = Modifier 90 | .road( 91 | whenCollapsed = Alignment.CenterStart, 92 | whenExpanded = Alignment.BottomEnd 93 | ) 94 | ) 95 | } 96 | ) { 97 | // ... 98 | } 99 | ``` 100 | The above code orders the title `Text` to be placed at the _CenterStart_ position when the toolbar is collapsed and _BottomEnd_ position when it is expanded. 101 | 102 | 103 | ## Scroll Strategy 104 | `ScrollStrategy` defines how CollapsingToolbar consumes scroll. You can set your desired behavior by providing `scrollStrategy` to `CollapsingToolbarScaffold`: 105 | 106 | ```kotlin 107 | CollapsingToolbarScaffold( 108 | /* ... */ 109 | scrollStrategy = ScrollStrategy.EnterAlways // EnterAlways, EnterAlwaysCollapsed, ExitUntilCollapsed are available 110 | ) { 111 | /* ... */ 112 | } 113 | ``` 114 | 115 | 116 | ### ScrollStrategy.EnterAlways 117 | ![EnterAlways](img/enter-always.gif) 118 | 119 | ### ScrollStrategy.EnterAlwaysCollapsed 120 | ![EnterAlwaysCollapsed](img/enter-always-collapsed.gif) 121 | 122 | ### ScrollStrategy.ExitUntilCollapsed 123 | ![ExitUntilCollapsed](img/exit-until-collapsed.gif) 124 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | compileSdkVersion 33 8 | buildToolsVersion "30.0.3" 9 | 10 | defaultConfig { 11 | applicationId "me.onebone.toolbar" 12 | minSdkVersion 21 13 | targetSdkVersion 33 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | vectorDrawables { 19 | useSupportLibrary true 20 | } 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | compileOptions { 30 | sourceCompatibility JavaVersion.VERSION_1_8 31 | targetCompatibility JavaVersion.VERSION_1_8 32 | } 33 | kotlinOptions { 34 | jvmTarget = '1.8' 35 | } 36 | buildFeatures { 37 | compose true 38 | } 39 | composeOptions { 40 | kotlinCompilerExtensionVersion compose_compiler_version 41 | } 42 | } 43 | 44 | dependencies { 45 | implementation 'androidx.core:core-ktx:1.9.0' 46 | implementation 'androidx.appcompat:appcompat:1.6.0' 47 | implementation 'com.google.android.material:material:1.7.0' 48 | 49 | implementation platform('androidx.compose:compose-bom:2023.01.00') 50 | implementation "androidx.compose.ui:ui" 51 | implementation "androidx.compose.material:material" 52 | implementation "androidx.compose.ui:ui-tooling" 53 | 54 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' 55 | implementation 'androidx.activity:activity-compose:1.6.1' 56 | 57 | testImplementation 'junit:junit:4.13.2' 58 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 59 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 60 | 61 | implementation project(':lib') 62 | } 63 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/me/onebone/toolbar/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package me.onebone.toolbar 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("me.onebone.toolbar", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/me/onebone/toolbar/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 onebone 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 20 | * OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | package me.onebone.toolbar 24 | 25 | import android.os.Bundle 26 | import androidx.activity.ComponentActivity 27 | import androidx.activity.compose.setContent 28 | import androidx.compose.foundation.Image 29 | import androidx.compose.foundation.background 30 | import androidx.compose.foundation.layout.* 31 | import androidx.compose.foundation.lazy.LazyColumn 32 | import androidx.compose.material.MaterialTheme 33 | import androidx.compose.material.Surface 34 | import androidx.compose.material.Text 35 | import androidx.compose.runtime.Composable 36 | import androidx.compose.ui.Alignment 37 | import androidx.compose.ui.Modifier 38 | import androidx.compose.ui.draw.alpha 39 | import androidx.compose.ui.graphics.Color 40 | import androidx.compose.ui.res.painterResource 41 | import androidx.compose.ui.unit.dp 42 | import androidx.compose.ui.unit.sp 43 | import me.onebone.toolbar.ui.theme.CollapsingToolbarTheme 44 | 45 | class MainActivity: ComponentActivity() { 46 | override fun onCreate(savedInstanceState: Bundle?) { 47 | super.onCreate(savedInstanceState) 48 | setContent { 49 | CollapsingToolbarTheme { 50 | // A surface container using the 'background' color from the theme 51 | Surface(color = MaterialTheme.colors.background) { 52 | MainScreen() 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | @Composable 60 | internal fun MainScreen() { 61 | val state = rememberCollapsingToolbarScaffoldState() 62 | 63 | CollapsingToolbarScaffold( 64 | modifier = Modifier 65 | .fillMaxSize(), 66 | state = state, 67 | scrollStrategy = ScrollStrategy.ExitUntilCollapsed, 68 | toolbar = { 69 | val textSize = (18 + (30 - 18) * state.toolbarState.progress).sp 70 | 71 | Box( 72 | modifier = Modifier 73 | .background(MaterialTheme.colors.primary) 74 | .fillMaxWidth() 75 | .height(150.dp) 76 | .pin() 77 | ) 78 | 79 | Text( 80 | text = "Title", 81 | modifier = Modifier 82 | .road(Alignment.CenterStart, Alignment.BottomEnd) 83 | .padding(60.dp, 16.dp, 16.dp, 16.dp), 84 | color = Color.White, 85 | fontSize = textSize 86 | ) 87 | 88 | Image( 89 | modifier = Modifier 90 | .pin() 91 | .padding(16.dp), 92 | painter = painterResource(id = R.drawable.abc_vector_test), 93 | contentDescription = null 94 | ) 95 | } 96 | ) { 97 | LazyColumn( 98 | modifier = Modifier 99 | .fillMaxWidth() 100 | ) { 101 | items(100) { 102 | Text( 103 | text = "Item $it", 104 | modifier = Modifier.padding(8.dp) 105 | ) 106 | } 107 | } 108 | 109 | Box( 110 | modifier = Modifier 111 | .fillMaxWidth() 112 | .alpha(0.5f) 113 | .background(MaterialTheme.colors.secondary) 114 | .height(40.dp) 115 | ) 116 | } 117 | 118 | CollapsingToolbarScaffold( 119 | modifier = Modifier.fillMaxSize(), 120 | state = rememberCollapsingToolbarScaffoldState(), 121 | scrollStrategy = ScrollStrategy.ExitUntilCollapsed, 122 | toolbar = { 123 | // toolbar contents... 124 | } 125 | ) { 126 | // body contents... 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /app/src/main/java/me/onebone/toolbar/ParallaxActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 onebone 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 20 | * OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | package me.onebone.toolbar 24 | 25 | import android.os.Bundle 26 | import androidx.activity.ComponentActivity 27 | import androidx.activity.compose.setContent 28 | import androidx.compose.foundation.Image 29 | import androidx.compose.foundation.background 30 | import androidx.compose.foundation.layout.Box 31 | import androidx.compose.foundation.layout.Row 32 | import androidx.compose.foundation.layout.Spacer 33 | import androidx.compose.foundation.layout.fillMaxSize 34 | import androidx.compose.foundation.layout.fillMaxWidth 35 | import androidx.compose.foundation.layout.height 36 | import androidx.compose.foundation.layout.padding 37 | import androidx.compose.foundation.lazy.LazyColumn 38 | import androidx.compose.foundation.lazy.items 39 | import androidx.compose.material.Button 40 | import androidx.compose.material.Checkbox 41 | import androidx.compose.material.MaterialTheme 42 | import androidx.compose.material.Surface 43 | import androidx.compose.material.Text 44 | import androidx.compose.runtime.Composable 45 | import androidx.compose.runtime.getValue 46 | import androidx.compose.runtime.mutableStateOf 47 | import androidx.compose.runtime.remember 48 | import androidx.compose.runtime.setValue 49 | import androidx.compose.ui.Alignment 50 | import androidx.compose.ui.Modifier 51 | import androidx.compose.ui.graphics.graphicsLayer 52 | import androidx.compose.ui.layout.ContentScale 53 | import androidx.compose.ui.res.painterResource 54 | import androidx.compose.ui.text.font.FontWeight 55 | import androidx.compose.ui.unit.dp 56 | import me.onebone.toolbar.ui.theme.CollapsingToolbarTheme 57 | 58 | class ParallaxActivity: ComponentActivity() { 59 | override fun onCreate(savedInstanceState: Bundle?) { 60 | super.onCreate(savedInstanceState) 61 | setContent { 62 | CollapsingToolbarTheme { 63 | Surface(color = MaterialTheme.colors.background) { 64 | ParallaxEffect() 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | @Composable 72 | fun ParallaxEffect() { 73 | val state = rememberCollapsingToolbarScaffoldState() 74 | 75 | var enabled by remember { mutableStateOf(true) } 76 | 77 | Box { 78 | CollapsingToolbarScaffold( 79 | modifier = Modifier.fillMaxSize(), 80 | state = state, 81 | scrollStrategy = ScrollStrategy.EnterAlwaysCollapsed, 82 | toolbarModifier = Modifier.background(MaterialTheme.colors.primary), 83 | enabled = enabled, 84 | toolbar = { 85 | // Collapsing toolbar collapses its size as small as the that of 86 | // a smallest child. To make the toolbar collapse to 50dp, we create 87 | // a dummy Spacer composable. 88 | // You may replace it with TopAppBar or other preferred composable. 89 | Spacer( 90 | modifier = Modifier 91 | .fillMaxWidth() 92 | .height(50.dp) 93 | ) 94 | 95 | Image( 96 | painter = painterResource(id = R.drawable.android), 97 | modifier = Modifier 98 | .parallax(0.5f) 99 | .height(300.dp) 100 | .graphicsLayer { 101 | // change alpha of Image as the toolbar expands 102 | alpha = state.toolbarState.progress 103 | }, 104 | contentScale = ContentScale.Crop, 105 | contentDescription = null 106 | ) 107 | } 108 | ) { 109 | LazyColumn( 110 | modifier = Modifier 111 | .fillMaxSize() 112 | ) { 113 | items( 114 | List(100) { "Hello World!! $it" } 115 | ) { 116 | Text( 117 | text = it, 118 | modifier = Modifier 119 | .fillMaxWidth() 120 | .padding(4.dp) 121 | ) 122 | } 123 | } 124 | 125 | @OptIn(ExperimentalToolbarApi::class) 126 | Button( 127 | modifier = Modifier 128 | .padding(16.dp) 129 | .align(Alignment.BottomEnd), 130 | onClick = { } 131 | ) { 132 | Text(text = "Floating Button!") 133 | } 134 | } 135 | 136 | Row( 137 | verticalAlignment = Alignment.CenterVertically 138 | ) { 139 | Checkbox(checked = enabled, onCheckedChange = { enabled = !enabled }) 140 | 141 | Text("Enable collapse/expand", fontWeight = FontWeight.Bold) 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /app/src/main/java/me/onebone/toolbar/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package me.onebone.toolbar.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Purple200 = Color(0xFFBB86FC) 6 | val Purple500 = Color(0xFF6200EE) 7 | val Purple700 = Color(0xFF3700B3) 8 | val Teal200 = Color(0xFF03DAC5) -------------------------------------------------------------------------------- /app/src/main/java/me/onebone/toolbar/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package me.onebone.toolbar.ui.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | val Shapes = Shapes( 8 | small = RoundedCornerShape(4.dp), 9 | medium = RoundedCornerShape(4.dp), 10 | large = RoundedCornerShape(0.dp) 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/onebone/toolbar/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package me.onebone.toolbar.ui.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.darkColors 6 | import androidx.compose.material.lightColors 7 | import androidx.compose.runtime.Composable 8 | 9 | private val DarkColorPalette = darkColors( 10 | primary = Purple200, 11 | primaryVariant = Purple700, 12 | secondary = Teal200 13 | ) 14 | 15 | private val LightColorPalette = lightColors( 16 | primary = Purple500, 17 | primaryVariant = Purple700, 18 | secondary = Teal200 19 | 20 | /* Other default colors to override 21 | background = Color.White, 22 | surface = Color.White, 23 | onPrimary = Color.White, 24 | onSecondary = Color.Black, 25 | onBackground = Color.Black, 26 | onSurface = Color.Black, 27 | */ 28 | ) 29 | 30 | @Composable 31 | fun CollapsingToolbarTheme( 32 | darkTheme: Boolean = isSystemInDarkTheme(), 33 | content: @Composable () -> Unit 34 | ) { 35 | val colors = if (darkTheme) { 36 | DarkColorPalette 37 | } else { 38 | LightColorPalette 39 | } 40 | 41 | MaterialTheme( 42 | colors = colors, 43 | typography = Typography, 44 | shapes = Shapes, 45 | content = content 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/me/onebone/toolbar/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package me.onebone.toolbar.ui.theme 2 | 3 | import androidx.compose.material.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | body1 = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp 15 | ) 16 | /* Other default text styles to override 17 | button = TextStyle( 18 | fontFamily = FontFamily.Default, 19 | fontWeight = FontWeight.W500, 20 | fontSize = 14.sp 21 | ), 22 | caption = TextStyle( 23 | fontFamily = FontFamily.Default, 24 | fontWeight = FontWeight.Normal, 25 | fontSize = 12.sp 26 | ) 27 | */ 28 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onebone/compose-collapsing-toolbar/5fefd2134c906bd649162b938ae6883a785e33e9/app/src/main/res/drawable/android.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onebone/compose-collapsing-toolbar/5fefd2134c906bd649162b938ae6883a785e33e9/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onebone/compose-collapsing-toolbar/5fefd2134c906bd649162b938ae6883a785e33e9/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onebone/compose-collapsing-toolbar/5fefd2134c906bd649162b938ae6883a785e33e9/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onebone/compose-collapsing-toolbar/5fefd2134c906bd649162b938ae6883a785e33e9/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onebone/compose-collapsing-toolbar/5fefd2134c906bd649162b938ae6883a785e33e9/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onebone/compose-collapsing-toolbar/5fefd2134c906bd649162b938ae6883a785e33e9/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onebone/compose-collapsing-toolbar/5fefd2134c906bd649162b938ae6883a785e33e9/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onebone/compose-collapsing-toolbar/5fefd2134c906bd649162b938ae6883a785e33e9/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onebone/compose-collapsing-toolbar/5fefd2134c906bd649162b938ae6883a785e33e9/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onebone/compose-collapsing-toolbar/5fefd2134c906bd649162b938ae6883a785e33e9/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Collapsing Toolbar 3 | MainActivity 4 | ParallaxActivity 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 21 | 22 |