├── app
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ └── themes.xml
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── drawable
│ │ │ ├── img_top_bar_dog_1.jpg
│ │ │ ├── img_top_bar_dog_2.jpg
│ │ │ ├── img_top_bar_dog_3.jpg
│ │ │ └── ic_launcher_background.xml
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ └── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── flaringapp
│ │ └── compose
│ │ └── topbar
│ │ ├── ui
│ │ ├── theme
│ │ │ ├── Color.kt
│ │ │ ├── Type.kt
│ │ │ └── Theme.kt
│ │ └── samples
│ │ │ ├── CollapsingTopBarSample.kt
│ │ │ ├── gallery
│ │ │ ├── SamplesGalleryGroup.kt
│ │ │ ├── SamplesGallery.kt
│ │ │ └── SamplesGalleryRow.kt
│ │ │ ├── CollapsingTopBarSampleDogDefaults.kt
│ │ │ ├── scaffold
│ │ │ ├── ScaffoldControlsTitleText.kt
│ │ │ ├── ScaffoldVisibilityControls.kt
│ │ │ ├── ScaffoldColumnDirectionControls.kt
│ │ │ ├── ScaffoldScrollControls.kt
│ │ │ └── ScaffoldStateControls.kt
│ │ │ ├── common
│ │ │ ├── SampleExpandRequestHandler.kt
│ │ │ ├── SampleTopBarImage.kt
│ │ │ ├── SampleVerticalFadingEdge.kt
│ │ │ ├── SampleFilterChips.kt
│ │ │ ├── SampleTopAppBar.kt
│ │ │ ├── SampleContent.kt
│ │ │ └── SampleTopBarBanner.kt
│ │ │ ├── basic
│ │ │ ├── EnterAlwaysCollapsedSample.kt
│ │ │ ├── BasicScaffoldSample.kt
│ │ │ ├── CollapsingSample.kt
│ │ │ └── CollapsingExitSample.kt
│ │ │ ├── advanced
│ │ │ ├── ParallaxCollapsingSample.kt
│ │ │ ├── SnapCollapsingSample.kt
│ │ │ ├── AppBarShadowSample.kt
│ │ │ ├── AppBarScrimSample.kt
│ │ │ ├── FloatingElementSample.kt
│ │ │ └── ManualCollapsingControlsSample.kt
│ │ │ ├── CollapsingTopBarSampleGroups.kt
│ │ │ └── column
│ │ │ ├── FullyCollapsibleColumnSample.kt
│ │ │ ├── PartiallyCollapsibleColumnSample.kt
│ │ │ ├── ColumnInStackSample.kt
│ │ │ ├── AlternatelyCollapsibleColumnSample.kt
│ │ │ └── ColumnMovingElementSample.kt
│ │ ├── Common.kt
│ │ └── MainActivity.kt
├── proguard-rules.pro
└── build.gradle.kts
├── ComposeCollapsingTopBar
├── .gitignore
├── consumer-rules.pro
├── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── flaringapp
│ │ │ └── compose
│ │ │ └── topbar
│ │ │ ├── CollapsingTopBarProgressListener.kt
│ │ │ ├── nestedscroll
│ │ │ ├── CollapsingTopBarNestedScrollHandler.kt
│ │ │ ├── CollapsingTopBarNestedScrollSnap.kt
│ │ │ ├── CollapsingTopBarNestedScrollCollapse.kt
│ │ │ ├── MultiNestedScrollConnection.kt
│ │ │ ├── CollapsingTopBarNestedScrollStrategy.kt
│ │ │ └── CollapsingTopBarNestedScrollExpand.kt
│ │ │ ├── snap
│ │ │ ├── CollapsingTopBarSnapScope.kt
│ │ │ └── CollapsingTopBarSnapBehavior.kt
│ │ │ ├── dependent
│ │ │ └── CollapsingTopBarDependentStateConnection.kt
│ │ │ ├── nestedcollapse
│ │ │ └── CollapsingTopBarNestedCollapseState.kt
│ │ │ ├── CollapsingTopBarControls.kt
│ │ │ └── scaffold
│ │ │ └── CollapsingTopBarScaffoldScrollMode.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── flaringapp
│ │ └── compose
│ │ └── topbar
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── docs
└── assets
│ ├── scrim.gif
│ ├── floating.gif
│ ├── manual.gif
│ ├── parallax.gif
│ ├── shadow.gif
│ ├── snapping.gif
│ ├── collapsing_mode_exit.gif
│ ├── collapsing_column_full.gif
│ ├── collapsing_column_stack.gif
│ ├── collapsing_mode_regular.gif
│ ├── cover_collapsing_column.gif
│ ├── cover_collapsing_stack.gif
│ ├── collapsing_column_reverse.gif
│ ├── collapsing_column_partially.gif
│ ├── collapsing_column_moving_element.gif
│ ├── collapsing_column_multiple_fixed.gif
│ ├── collapsing_mode_exit_expand_always.gif
│ ├── collapsing_mode_enter_always_collapsed.gif
│ └── collapsing_mode_regular_expand_always.gif
├── .idea
├── copyright
│ ├── profiles_settings.xml
│ └── Apache.xml
└── scopes
│ └── Copyright_Scope.xml
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── libs.versions.toml
├── .gitignore
├── .github
└── workflows
│ ├── ci.yml
│ └── publish-release.yml
├── settings.gradle.kts
├── gradle.properties
├── .editorconfig
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ComposeCollapsingTopBar/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ComposeCollapsingTopBar/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ComposeCollapsingTopBar/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/assets/scrim.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/docs/assets/scrim.gif
--------------------------------------------------------------------------------
/docs/assets/floating.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/docs/assets/floating.gif
--------------------------------------------------------------------------------
/docs/assets/manual.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/docs/assets/manual.gif
--------------------------------------------------------------------------------
/docs/assets/parallax.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/docs/assets/parallax.gif
--------------------------------------------------------------------------------
/docs/assets/shadow.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/docs/assets/shadow.gif
--------------------------------------------------------------------------------
/docs/assets/snapping.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/docs/assets/snapping.gif
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ComposeCollapsingTopBar
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/docs/assets/collapsing_mode_exit.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/docs/assets/collapsing_mode_exit.gif
--------------------------------------------------------------------------------
/docs/assets/collapsing_column_full.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/docs/assets/collapsing_column_full.gif
--------------------------------------------------------------------------------
/docs/assets/collapsing_column_stack.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/docs/assets/collapsing_column_stack.gif
--------------------------------------------------------------------------------
/docs/assets/collapsing_mode_regular.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/docs/assets/collapsing_mode_regular.gif
--------------------------------------------------------------------------------
/docs/assets/cover_collapsing_column.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/docs/assets/cover_collapsing_column.gif
--------------------------------------------------------------------------------
/docs/assets/cover_collapsing_stack.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/docs/assets/cover_collapsing_stack.gif
--------------------------------------------------------------------------------
/docs/assets/collapsing_column_reverse.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/docs/assets/collapsing_column_reverse.gif
--------------------------------------------------------------------------------
/docs/assets/collapsing_column_partially.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/docs/assets/collapsing_column_partially.gif
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/drawable/img_top_bar_dog_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/app/src/main/res/drawable/img_top_bar_dog_1.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/img_top_bar_dog_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/app/src/main/res/drawable/img_top_bar_dog_2.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/img_top_bar_dog_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/app/src/main/res/drawable/img_top_bar_dog_3.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/docs/assets/collapsing_column_moving_element.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/docs/assets/collapsing_column_moving_element.gif
--------------------------------------------------------------------------------
/docs/assets/collapsing_column_multiple_fixed.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/docs/assets/collapsing_column_multiple_fixed.gif
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/docs/assets/collapsing_mode_exit_expand_always.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/docs/assets/collapsing_mode_exit_expand_always.gif
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/docs/assets/collapsing_mode_enter_always_collapsed.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/docs/assets/collapsing_mode_enter_always_collapsed.gif
--------------------------------------------------------------------------------
/docs/assets/collapsing_mode_regular_expand_always.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaringapp/ComposeCollapsingTopBar/HEAD/docs/assets/collapsing_mode_regular_expand_always.gif
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/*
5 | !/.idea/scopes
6 | !/.idea/copyright
7 | .DS_Store
8 | /build
9 | /.kotlin
10 | /captures
11 | .externalNativeBuild
12 | .cxx
13 | local.properties
14 |
--------------------------------------------------------------------------------
/.idea/scopes/Copyright_Scope.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Run PR checks
2 |
3 | on:
4 | pull_request:
5 | branches: [ main ]
6 |
7 | jobs:
8 | lint_verify:
9 | name: Run linter checks
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v4
15 |
16 | - name: Set up JDK 17
17 | uses: actions/setup-java@v4
18 | with:
19 | distribution: 'temurin'
20 | java-version: 17
21 |
22 | - name: Grant execute permission for gradlew
23 | run: chmod +x ./gradlew
24 |
25 | - name: Run ktlint Linter
26 | run: ./gradlew ktlintCheck
27 |
28 | - name: Run Android Linter
29 | run: ./gradlew lintRelease
30 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | pluginManagement {
4 | repositories {
5 | google {
6 | content {
7 | includeGroupByRegex("com\\.android.*")
8 | includeGroupByRegex("com\\.google.*")
9 | includeGroupByRegex("androidx.*")
10 | }
11 | }
12 | mavenCentral()
13 | gradlePluginPortal()
14 | }
15 | }
16 | dependencyResolutionManagement {
17 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
18 | repositories {
19 | google()
20 | mavenCentral()
21 | }
22 | }
23 |
24 | rootProject.name = "ComposeCollapsingTopBar"
25 | include(":app")
26 | include(":ComposeCollapsingTopBar")
27 |
--------------------------------------------------------------------------------
/.idea/copyright/Apache.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/ComposeCollapsingTopBar/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
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2025 Flaringapp
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 | #Fri Aug 15 21:56:26 EEST 2025
18 | distributionBase=GRADLE_USER_HOME
19 | distributionPath=wrapper/dists
20 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
21 | zipStoreBase=GRADLE_USER_HOME
22 | zipStorePath=wrapper/dists
23 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
14 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.theme
18 |
19 | import androidx.compose.ui.graphics.Color
20 |
21 | val Purple80 = Color(0xFFD0BCFF)
22 | val PurpleGrey80 = Color(0xFFCCC2DC)
23 | val Pink80 = Color(0xFFEFB8C8)
24 |
25 | val Purple40 = Color(0xFF6650a4)
26 | val PurpleGrey40 = Color(0xFF625b71)
27 | val Pink40 = Color(0xFF7D5260)
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/CollapsingTopBarSample.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.Immutable
21 |
22 | @Immutable
23 | interface CollapsingTopBarSample {
24 |
25 | val name: String
26 |
27 | @Composable
28 | fun Content(
29 | onBack: () -> Unit,
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/gallery/SamplesGalleryGroup.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.gallery
18 |
19 | import androidx.compose.runtime.snapshots.SnapshotStateList
20 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSample
21 |
22 | data class SamplesGalleryGroup(
23 | val name: String,
24 | val samples: SnapshotStateList,
25 | )
26 |
--------------------------------------------------------------------------------
/.github/workflows/publish-release.yml:
--------------------------------------------------------------------------------
1 | name: Publish Maven Central release
2 |
3 | on:
4 | push:
5 | tags:
6 | - '**'
7 |
8 | jobs:
9 | publish:
10 | name: Publish release
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 |
17 | - name: Set up JDK 17
18 | uses: actions/setup-java@v4
19 | with:
20 | distribution: 'temurin'
21 | java-version: 17
22 |
23 | - name: Grant execute permission for gradlew
24 | run: chmod +x ./gradlew
25 |
26 | - name: Publish to Maven Central
27 | run: ./gradlew publishAndReleaseToMavenCentral
28 | env:
29 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
30 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
31 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }}
32 | ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_KEY_ID }}
33 | ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/CollapsingTopBarSampleDogDefaults.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples
18 |
19 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopBarImageDog
20 |
21 | object CollapsingTopBarSampleDogDefaults {
22 |
23 | val Basic: SampleTopBarImageDog
24 | get() = SampleTopBarImageDog.GoldenMartian
25 |
26 | val Advanced: SampleTopBarImageDog
27 | get() = SampleTopBarImageDog.LabradorInCar
28 |
29 | val Column: SampleTopBarImageDog
30 | get() = SampleTopBarImageDog.Dachshund
31 | }
32 |
--------------------------------------------------------------------------------
/ComposeCollapsingTopBar/src/main/java/com/flaringapp/compose/topbar/CollapsingTopBarProgressListener.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar
18 |
19 | /**
20 | * Receives collapsing progress updates in [onProgressUpdate].
21 | */
22 | fun interface CollapsingTopBarProgressListener {
23 |
24 | /**
25 | * Callback to collapsing progress update.
26 | *
27 | * @param totalProgress progress of total top bar collapsing.
28 | * @param itemProgress progress of target item collapsing.
29 | */
30 | fun onProgressUpdate(
31 | totalProgress: Float,
32 | itemProgress: Float,
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/Common.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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 | // https://github.com/slackhq/compose-lints/issues/326
18 | @file:SuppressLint("ComposeUnstableReceiver")
19 |
20 | package com.flaringapp.compose.topbar
21 |
22 | import android.annotation.SuppressLint
23 | import androidx.compose.foundation.layout.WindowInsets
24 | import androidx.compose.foundation.layout.displayCutout
25 | import androidx.compose.foundation.layout.systemBars
26 | import androidx.compose.foundation.layout.union
27 | import androidx.compose.runtime.Composable
28 | import androidx.compose.runtime.NonRestartableComposable
29 |
30 | val WindowInsets.Companion.screen: WindowInsets
31 | @Composable
32 | @NonRestartableComposable
33 | get() = WindowInsets.systemBars.union(WindowInsets.displayCutout)
34 |
--------------------------------------------------------------------------------
/ComposeCollapsingTopBar/src/androidTest/java/com/flaringapp/compose/topbar/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar
18 |
19 | import androidx.test.ext.junit.runners.AndroidJUnit4
20 | import androidx.test.platform.app.InstrumentationRegistry
21 | import org.junit.Assert.assertEquals
22 | import org.junit.Test
23 | import org.junit.runner.RunWith
24 |
25 | /**
26 | * Instrumented test, which will execute on an Android device.
27 | *
28 | * See [testing documentation](http://d.android.com/tools/testing).
29 | */
30 | @RunWith(AndroidJUnit4::class)
31 | class ExampleInstrumentedTest {
32 | @Test
33 | fun useAppContext() {
34 | // Context of the app under test.
35 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
36 | assertEquals("com.flaringapp.compose.topbar.test", appContext.packageName)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/ComposeCollapsingTopBar/src/main/java/com/flaringapp/compose/topbar/nestedscroll/CollapsingTopBarNestedScrollHandler.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.nestedscroll
18 |
19 | import androidx.compose.foundation.gestures.FlingBehavior
20 | import androidx.compose.foundation.gestures.ScrollableState
21 | import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
22 |
23 | /**
24 | * A contract for single top bar nested scroll handler responsible for processing single logical
25 | * operation (e.g. expanding). A perfect place to define common extensions for all handlers.
26 | */
27 | interface CollapsingTopBarNestedScrollHandler : NestedScrollConnection {
28 |
29 | suspend fun ScrollableState.fling(
30 | flingBehavior: FlingBehavior,
31 | velocity: Float,
32 | ): Float {
33 | var left = velocity
34 | scroll {
35 | with(flingBehavior) {
36 | left = performFling(left)
37 | }
38 | }
39 |
40 | return left
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/scaffold/ScaffoldControlsTitleText.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.scaffold
18 |
19 | import androidx.compose.material3.MaterialTheme
20 | import androidx.compose.material3.Text
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.tooling.preview.Preview
24 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
25 |
26 | @Composable
27 | fun ScaffoldControlsTitleText(
28 | text: String,
29 | modifier: Modifier = Modifier,
30 | ) {
31 | Text(
32 | modifier = modifier,
33 | text = text,
34 | color = MaterialTheme.colorScheme.primary,
35 | style = MaterialTheme.typography.titleLarge,
36 | )
37 | }
38 |
39 | @Preview(showBackground = true)
40 | @Composable
41 | private fun Preview() {
42 | ComposeCollapsingTopBarTheme {
43 | ScaffoldControlsTitleText(
44 | text = "Controls Text",
45 | )
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/common/SampleExpandRequestHandler.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.common
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.LaunchedEffect
21 | import androidx.compose.runtime.MutableState
22 | import androidx.compose.runtime.mutableStateOf
23 | import androidx.compose.runtime.remember
24 | import com.flaringapp.compose.topbar.CollapsingTopBarControls
25 |
26 | @Composable
27 | fun rememberSampleExpandRequestHandler(
28 | controls: CollapsingTopBarControls,
29 | ): MutableState {
30 | val expandRequestState: MutableState = remember {
31 | mutableStateOf(null)
32 | }
33 |
34 | LaunchedEffect(expandRequestState.value) {
35 | val expand = expandRequestState.value ?: return@LaunchedEffect
36 |
37 | if (expand) {
38 | controls.expand()
39 | } else {
40 | controls.collapse()
41 | }
42 |
43 | expandRequestState.value = null
44 | }
45 |
46 | return expandRequestState
47 | }
48 |
--------------------------------------------------------------------------------
/ComposeCollapsingTopBar/src/main/java/com/flaringapp/compose/topbar/snap/CollapsingTopBarSnapScope.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.snap
18 |
19 | import com.flaringapp.compose.topbar.CollapsingTopBarControls
20 |
21 | /**
22 | * The receiver scope of [CollapsingTopBarSnapBehavior]. Creates a scoped snapping environment of
23 | * something that represents top bar state as [CollapsingTopBarControls], which even may be
24 | * dynamic. Responsible for executing snap action requested in [snapWithProgress].
25 | */
26 | fun interface CollapsingTopBarSnapScope {
27 |
28 | /**
29 | * Executes snap action [action] providing current progress with [CollapsingTopBarControls]
30 | * receiver.
31 | *
32 | * @param wasMovingUp the last motion direction that resulted in snap being requested. May
33 | * help complex snap scopes decide where to snap, i.e. what [CollapsingTopBarControls] to use.
34 | * @param action the snap action lambda to be executed.
35 | */
36 | suspend fun snapWithProgress(
37 | wasMovingUp: Boolean,
38 | action: suspend CollapsingTopBarControls.(progress: Float) -> Unit,
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.theme
18 |
19 | import androidx.compose.material3.Typography
20 | import androidx.compose.ui.text.TextStyle
21 | import androidx.compose.ui.text.font.FontFamily
22 | import androidx.compose.ui.text.font.FontWeight
23 | import androidx.compose.ui.unit.sp
24 |
25 | // Set of Material typography styles to start with
26 | val Typography = Typography(
27 | bodyLarge = TextStyle(
28 | fontFamily = FontFamily.Default,
29 | fontWeight = FontWeight.Normal,
30 | fontSize = 16.sp,
31 | lineHeight = 24.sp,
32 | letterSpacing = 0.5.sp,
33 | ),
34 | /* Other default text styles to override
35 | titleLarge = TextStyle(
36 | fontFamily = FontFamily.Default,
37 | fontWeight = FontWeight.Normal,
38 | fontSize = 22.sp,
39 | lineHeight = 28.sp,
40 | letterSpacing = 0.sp
41 | ),
42 | labelSmall = TextStyle(
43 | fontFamily = FontFamily.Default,
44 | fontWeight = FontWeight.Medium,
45 | fontSize = 11.sp,
46 | lineHeight = 16.sp,
47 | letterSpacing = 0.5.sp
48 | )
49 | */
50 | )
51 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.11.1"
3 | kotlin = "2.2.10"
4 |
5 | [libraries]
6 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version = "1.17.0" }
7 | androidx-junit = { group = "androidx.test.ext", name = "junit", version = "1.3.0" }
8 | androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version = "3.7.0" }
9 | androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version = "1.10.1" }
10 |
11 | androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version = "2025.08.00" }
12 | androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" }
13 | androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
14 | androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
15 | androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
16 | androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
17 | androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
18 | androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
19 |
20 | slack-compose-linter = { group = "com.slack.lint.compose", name = "compose-lint-checks", version = "1.4.2" }
21 |
22 | [plugins]
23 | android-application = { id = "com.android.application", version.ref = "agp" }
24 | android-library = { id = "com.android.library", version.ref = "agp" }
25 | jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
26 | jetbrains-kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
27 | ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "13.0.0" }
28 | vanniktech-maven-publish = { id = "com.vanniktech.maven.publish", version = "0.34.0" }
29 |
--------------------------------------------------------------------------------
/ComposeCollapsingTopBar/src/main/java/com/flaringapp/compose/topbar/nestedscroll/CollapsingTopBarNestedScrollSnap.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.nestedscroll
18 |
19 | import androidx.compose.ui.unit.Velocity
20 | import com.flaringapp.compose.topbar.snap.CollapsingTopBarSnapBehavior
21 | import com.flaringapp.compose.topbar.snap.CollapsingTopBarSnapScope
22 |
23 | /**
24 | * A nested scroll handler that executes top bar snapping after fling has ended on scope
25 | * [snapScope] with behavior [snapBehavior].
26 | *
27 | * @param snapBehavior the snap behavior to be used for snapping with [snapScope] after fling.
28 | * @param snapScope the receiver scope to be used for executing snap animation with [snapBehavior].
29 | */
30 | class CollapsingTopBarNestedScrollSnap(
31 | private val snapBehavior: CollapsingTopBarSnapBehavior,
32 | private val snapScope: CollapsingTopBarSnapScope,
33 | ) : CollapsingTopBarNestedScrollHandler {
34 |
35 | override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
36 | if (available.y != 0f) return Velocity.Zero
37 |
38 | val wasMovingUp = consumed.y < 0
39 |
40 | with(snapBehavior) {
41 | snapScope.snap(wasMovingUp)
42 | }
43 |
44 | return Velocity(0f, available.y)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2 |
3 | plugins {
4 | alias(libs.plugins.android.application)
5 | alias(libs.plugins.jetbrains.kotlin.android)
6 | alias(libs.plugins.jetbrains.kotlin.compose)
7 | }
8 |
9 | android {
10 | namespace = "com.flaringapp.compose.topbar"
11 | compileSdk = 36
12 |
13 | defaultConfig {
14 | applicationId = "com.flaringapp.compose.topbar"
15 | minSdk = 21
16 | targetSdk = 36
17 | versionCode = 1
18 | versionName = "1.0"
19 |
20 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
21 | vectorDrawables {
22 | useSupportLibrary = true
23 | }
24 | }
25 |
26 | buildFeatures {
27 | compose = true
28 | }
29 | buildTypes {
30 | release {
31 | isMinifyEnabled = false
32 | proguardFiles(
33 | getDefaultProguardFile("proguard-android-optimize.txt"),
34 | "proguard-rules.pro",
35 | )
36 | }
37 | }
38 | compileOptions {
39 | sourceCompatibility = JavaVersion.VERSION_17
40 | targetCompatibility = JavaVersion.VERSION_17
41 | }
42 | composeCompiler {
43 | reportsDestination = layout.buildDirectory.dir("compose_reports")
44 | }
45 | packaging {
46 | resources {
47 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
48 | }
49 | }
50 | }
51 |
52 | kotlin {
53 | compilerOptions {
54 | jvmTarget.set(JvmTarget.JVM_17)
55 | }
56 | }
57 |
58 | dependencies {
59 | implementation(project(":ComposeCollapsingTopBar"))
60 |
61 | implementation(libs.androidx.core.ktx)
62 | implementation(libs.androidx.activity.compose)
63 | implementation(platform(libs.androidx.compose.bom))
64 | implementation(libs.androidx.compose.ui)
65 | implementation(libs.androidx.compose.ui.tooling.preview)
66 | implementation(libs.androidx.compose.material3)
67 | debugImplementation(libs.androidx.compose.ui.tooling)
68 |
69 | lintChecks(libs.slack.compose.linter)
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/basic/EnterAlwaysCollapsedSample.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.basic
18 |
19 | import androidx.compose.foundation.layout.fillMaxSize
20 | import androidx.compose.material3.Surface
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.tooling.preview.Preview
24 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldScrollMode
25 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSample
26 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
27 |
28 | object EnterAlwaysCollapsedSample : CollapsingTopBarSample {
29 |
30 | override val name: String = "Enter always collapsed"
31 |
32 | @Composable
33 | override fun Content(onBack: () -> Unit) {
34 | EnterAlwaysCollapsedSampleContent(onBack = onBack)
35 | }
36 | }
37 |
38 | @Composable
39 | fun EnterAlwaysCollapsedSampleContent(
40 | onBack: () -> Unit,
41 | modifier: Modifier = Modifier,
42 | ) {
43 | Surface(
44 | modifier = modifier.fillMaxSize(),
45 | ) {
46 | BasicScaffoldSampleContent(
47 | title = "Enter Always Collapsed",
48 | onBack = onBack,
49 | scrollMode = CollapsingTopBarScaffoldScrollMode.enterAlwaysCollapsed(),
50 | )
51 | }
52 | }
53 |
54 | @Preview
55 | @Composable
56 | private fun Preview() {
57 | ComposeCollapsingTopBarTheme {
58 | EnterAlwaysCollapsedSampleContent(
59 | onBack = {},
60 | )
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/common/SampleTopBarImage.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.common
18 |
19 | import androidx.compose.foundation.Image
20 | import androidx.compose.foundation.layout.height
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.layout.ContentScale
24 | import androidx.compose.ui.res.painterResource
25 | import androidx.compose.ui.tooling.preview.Preview
26 | import androidx.compose.ui.unit.Dp
27 | import androidx.compose.ui.unit.dp
28 | import com.flaringapp.compose.topbar.R
29 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
30 |
31 | sealed class SampleTopBarImageDog(
32 | val imageRes: Int,
33 | ) {
34 | data object GoldenMartian : SampleTopBarImageDog(R.drawable.img_top_bar_dog_1)
35 | data object LabradorInCar : SampleTopBarImageDog(R.drawable.img_top_bar_dog_2)
36 | data object Dachshund : SampleTopBarImageDog(R.drawable.img_top_bar_dog_3)
37 | }
38 |
39 | @Composable
40 | fun SampleTopBarImage(
41 | dog: SampleTopBarImageDog,
42 | modifier: Modifier = Modifier,
43 | height: Dp = 300.dp,
44 | ) {
45 | Image(
46 | modifier = modifier.height(height),
47 | painter = painterResource(dog.imageRes),
48 | contentDescription = null,
49 | contentScale = ContentScale.FillHeight,
50 | )
51 | }
52 |
53 | @Preview
54 | @Composable
55 | private fun Preview() {
56 | ComposeCollapsingTopBarTheme {
57 | SampleTopBarImage(
58 | dog = SampleTopBarImageDog.GoldenMartian,
59 | )
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/ComposeCollapsingTopBar/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.vanniktech.maven.publish.AndroidSingleVariantLibrary
2 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
3 |
4 | plugins {
5 | alias(libs.plugins.android.library)
6 | alias(libs.plugins.jetbrains.kotlin.android)
7 | alias(libs.plugins.jetbrains.kotlin.compose)
8 | alias(libs.plugins.vanniktech.maven.publish)
9 | }
10 |
11 | android {
12 | namespace = "com.flaringapp.compose.topbar"
13 | compileSdk = 36
14 |
15 | defaultConfig {
16 | minSdk = 21
17 |
18 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
19 | consumerProguardFiles("consumer-rules.pro")
20 |
21 | aarMetadata {
22 | minCompileSdk = minSdk
23 | }
24 | }
25 |
26 | buildTypes {
27 | release {
28 | isMinifyEnabled = false
29 | proguardFiles(
30 | getDefaultProguardFile("proguard-android-optimize.txt"),
31 | "proguard-rules.pro",
32 | )
33 | }
34 | }
35 | compileOptions {
36 | sourceCompatibility = JavaVersion.VERSION_17
37 | targetCompatibility = JavaVersion.VERSION_17
38 | }
39 | composeCompiler {
40 | reportsDestination = layout.buildDirectory.dir("compose_reports")
41 | }
42 | }
43 |
44 | kotlin {
45 | compilerOptions {
46 | jvmTarget.set(JvmTarget.JVM_17)
47 | }
48 | }
49 |
50 | dependencies {
51 | implementation(platform(libs.androidx.compose.bom))
52 | implementation(libs.androidx.compose.foundation)
53 | implementation(libs.androidx.compose.ui)
54 | implementation(libs.androidx.compose.ui.tooling.preview)
55 | androidTestImplementation(libs.androidx.junit)
56 | androidTestImplementation(libs.androidx.espresso.core)
57 | androidTestImplementation(platform(libs.androidx.compose.bom))
58 | androidTestImplementation(libs.androidx.compose.ui.test.junit4)
59 | debugImplementation(libs.androidx.compose.ui.tooling)
60 | debugImplementation(libs.androidx.compose.ui.test.manifest)
61 |
62 | lintChecks(libs.slack.compose.linter)
63 | }
64 |
65 | mavenPublishing {
66 | configure(
67 | AndroidSingleVariantLibrary(
68 | variant = "release",
69 | sourcesJar = true,
70 | publishJavadocJar = true,
71 | ),
72 | )
73 | }
74 |
--------------------------------------------------------------------------------
/ComposeCollapsingTopBar/src/main/java/com/flaringapp/compose/topbar/dependent/CollapsingTopBarDependentStateConnection.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.dependent
18 |
19 | import androidx.compose.runtime.snapshots.Snapshot
20 | import androidx.compose.ui.Modifier
21 | import androidx.compose.ui.layout.layout
22 | import androidx.compose.ui.unit.IntOffset
23 | import com.flaringapp.compose.topbar.CollapsingTopBarLayoutInfo
24 | import com.flaringapp.compose.topbar.CollapsingTopBarState
25 |
26 | /**
27 | * A Modifier that allows connecting custom derived top bar state to [CollapsingTopBarState] by
28 | * receiving [CollapsingTopBarLayoutInfo] measurement updates. Useful to keep track of min/max
29 | * height.
30 | *
31 | * It's recommended to use this modifier instead of direct [CollapsingTopBarState.layoutInfo]
32 | * access (which is backed by state) to avoid unnecessary recompositions.
33 | *
34 | * **This modifier does not update on placement phase, therefore it will not keep track of
35 | * top bar height.**
36 | *
37 | * @param state the top bar state to observe.
38 | * @param update the action for updating custom derived state on re-measurement, when [state]
39 | * layout info is updated.
40 | */
41 | fun Modifier.collapsingTopBarDependentStateConnection(
42 | state: CollapsingTopBarState,
43 | update: (layoutInfo: CollapsingTopBarLayoutInfo) -> Unit,
44 | ): Modifier = this.layout { measurable, constraints ->
45 | val placeable = measurable.measure(constraints)
46 |
47 | val lastLayoutInfo = Snapshot.withoutReadObservation { state.layoutInfo }
48 | update(lastLayoutInfo)
49 |
50 | layout(placeable.width, placeable.height) {
51 | placeable.place(IntOffset.Zero)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
24 |
25 | mavenCentralPublishing=true
26 | signAllPublications=true
27 |
28 | GROUP=io.github.flaringapp
29 | POM_ARTIFACT_ID=ComposeCollapsingTopBar
30 | VERSION_NAME=1.2.0
31 |
32 | POM_NAME=ComposeCollapsingTopBar
33 | POM_DESCRIPTION=The ultimate collapsing toolbar library with dynamic sizing, different scroll modes, and extensive customization options
34 | POM_INCEPTION_YEAR=2025
35 | POM_URL=https://github.com/flaringapp/ComposeCollapsingTopBar
36 |
37 | POM_LICENSE_NAME=The Apache Software License, Version 2.0
38 | POM_LICENSE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt
39 | POM_LICENSE_DIST=repo
40 |
41 | POM_SCM_URL=https://github.com/flaringapp/ComposeCollapsingTopBar
42 | POM_SCM_CONNECTION=scm:git:git://github.com/flaringapp/ComposeCollapsingTopBar.git
43 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/flaringapp/ComposeCollapsingTopBar.git
44 |
45 | POM_DEVELOPER_ID=flaringapp
46 | POM_DEVELOPER_NAME=Flaringapp
47 | POM_DEVELOPER_URL=https://github.com/flaringapp
48 |
--------------------------------------------------------------------------------
/ComposeCollapsingTopBar/src/main/java/com/flaringapp/compose/topbar/nestedscroll/CollapsingTopBarNestedScrollCollapse.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.nestedscroll
18 |
19 | import androidx.compose.foundation.gestures.FlingBehavior
20 | import androidx.compose.foundation.gestures.ScrollableState
21 | import androidx.compose.ui.geometry.Offset
22 | import androidx.compose.ui.input.nestedscroll.NestedScrollSource
23 | import androidx.compose.ui.unit.Velocity
24 |
25 | /**
26 | * A top bar nested scroll handler that collapses [state] while receiving pre- scroll and fling.
27 | * Consumes available scroll before children, so that top bar collapses anywhere.
28 | *
29 | * Collapsing in terms of this handler means dispatching scroll to [state], which is in its turn
30 | * responsible for further processing.
31 | *
32 | * @param state the scrollable top bar state that collapses.
33 | * @param flingBehavior the fling behavior to be used for animating [state] fling.
34 | */
35 | class CollapsingTopBarNestedScrollCollapse(
36 | private val state: ScrollableState,
37 | private val flingBehavior: FlingBehavior,
38 | ) : CollapsingTopBarNestedScrollHandler {
39 |
40 | override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
41 | val dy = available.y
42 | if (dy >= 0) {
43 | return Offset.Zero
44 | }
45 |
46 | val consume = state.dispatchRawDelta(dy)
47 | return Offset(x = 0f, y = consume)
48 | }
49 |
50 | override suspend fun onPreFling(available: Velocity): Velocity {
51 | val dy = available.y
52 | if (dy >= 0) {
53 | return Velocity.Zero
54 | }
55 |
56 | val consume = dy - state.fling(flingBehavior, dy)
57 | return Velocity(x = 0f, y = consume)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/common/SampleVerticalFadingEdge.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.common
18 |
19 | import androidx.compose.foundation.background
20 | import androidx.compose.foundation.layout.Box
21 | import androidx.compose.foundation.layout.fillMaxWidth
22 | import androidx.compose.foundation.layout.height
23 | import androidx.compose.material3.MaterialTheme
24 | import androidx.compose.runtime.Composable
25 | import androidx.compose.runtime.remember
26 | import androidx.compose.ui.Modifier
27 | import androidx.compose.ui.graphics.Brush
28 | import androidx.compose.ui.graphics.Color
29 | import androidx.compose.ui.layout.layout
30 | import androidx.compose.ui.tooling.preview.Preview
31 | import androidx.compose.ui.unit.Dp
32 | import androidx.compose.ui.unit.IntOffset
33 | import androidx.compose.ui.unit.dp
34 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
35 |
36 | @Composable
37 | fun SampleVerticalFadingEdge(
38 | modifier: Modifier = Modifier,
39 | height: Dp = 12.dp,
40 | color: Color = MaterialTheme.colorScheme.surface,
41 | ) {
42 | val brush = remember(color) {
43 | Brush.verticalGradient(
44 | listOf(color, color.copy(alpha = 0f)),
45 | )
46 | }
47 |
48 | Box(
49 | modifier = modifier
50 | .layout { measurable, constraints ->
51 | val placeable = measurable.measure(constraints)
52 | layout(placeable.width, 0) {
53 | placeable.place(IntOffset.Zero)
54 | }
55 | }
56 | .fillMaxWidth()
57 | .height(height)
58 | .background(brush),
59 | )
60 | }
61 |
62 | @Preview
63 | @Composable
64 | private fun Preview() {
65 | ComposeCollapsingTopBarTheme {
66 | Box(modifier = Modifier.height(40.dp)) {
67 | SampleVerticalFadingEdge()
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/ComposeCollapsingTopBar/src/main/java/com/flaringapp/compose/topbar/nestedcollapse/CollapsingTopBarNestedCollapseState.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.nestedcollapse
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.Stable
21 | import androidx.compose.runtime.annotation.RememberInComposition
22 | import androidx.compose.runtime.remember
23 | import com.flaringapp.compose.topbar.CollapsingTopBar
24 |
25 | /**
26 | * Creates a [CollapsingTopBarNestedCollapseState] that is remembered across compositions.
27 | */
28 | @Composable
29 | fun rememberCollapsingTopBarNestedCollapseState(): CollapsingTopBarNestedCollapseState {
30 | return remember {
31 | CollapsingTopBarNestedCollapseState()
32 | }
33 | }
34 |
35 | /**
36 | * A contract for any top bar [CollapsingTopBar] nested collapse element to provide its own
37 | * minimum height. Used in [CollapsingTopBar] to determine ultimate minimum top bar height.
38 | *
39 | * @see CollapsingTopBar
40 | * @see CollapsingTopBarColumn
41 | */
42 | @Stable
43 | interface CollapsingTopBarNestedCollapseElement {
44 |
45 | /**
46 | * The minimum height of this nested collapse element.
47 | */
48 | val minHeight: Int
49 | }
50 |
51 | /**
52 | * A mutable implementation of [CollapsingTopBarNestedCollapseElement] for nested collapse element
53 | * to update. Must be updated in measurement phase.
54 | *
55 | * In most cases, this will be created via [rememberCollapsingTopBarNestedCollapseState].
56 | */
57 | @Stable
58 | class CollapsingTopBarNestedCollapseState @RememberInComposition constructor() :
59 | CollapsingTopBarNestedCollapseElement {
60 |
61 | /**
62 | * The minimum height of this nested collapse element. Must be updated in measurement phase.
63 | * Not a state because hosting [CollapsingTopBar] is remeasured every time after this element,
64 | * therefore reads up to date value anyways.
65 | */
66 | override var minHeight: Int = 0
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.theme
18 |
19 | import android.os.Build
20 | import androidx.compose.foundation.isSystemInDarkTheme
21 | import androidx.compose.material3.MaterialTheme
22 | import androidx.compose.material3.darkColorScheme
23 | import androidx.compose.material3.dynamicDarkColorScheme
24 | import androidx.compose.material3.dynamicLightColorScheme
25 | import androidx.compose.material3.lightColorScheme
26 | import androidx.compose.runtime.Composable
27 | import androidx.compose.ui.platform.LocalContext
28 |
29 | private val DarkColorScheme = darkColorScheme(
30 | primary = Purple80,
31 | secondary = PurpleGrey80,
32 | tertiary = Pink80,
33 | )
34 |
35 | private val LightColorScheme = lightColorScheme(
36 | primary = Purple40,
37 | secondary = PurpleGrey40,
38 | tertiary = Pink40,
39 |
40 | /* Other default colors to override
41 | background = Color(0xFFFFFBFE),
42 | surface = Color(0xFFFFFBFE),
43 | onPrimary = Color.White,
44 | onSecondary = Color.White,
45 | onTertiary = Color.White,
46 | onBackground = Color(0xFF1C1B1F),
47 | onSurface = Color(0xFF1C1B1F),
48 | */
49 | )
50 |
51 | @Composable
52 | fun ComposeCollapsingTopBarTheme(
53 | darkTheme: Boolean = isSystemInDarkTheme(),
54 | // Dynamic color is available on Android 12+
55 | dynamicColor: Boolean = true,
56 | content: @Composable () -> Unit,
57 | ) {
58 | val colorScheme = when {
59 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
60 | val context = LocalContext.current
61 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
62 | }
63 |
64 | darkTheme -> DarkColorScheme
65 | else -> LightColorScheme
66 | }
67 |
68 | MaterialTheme(
69 | colorScheme = colorScheme,
70 | typography = Typography,
71 | content = content,
72 | )
73 | }
74 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | charset = utf-8
3 | end_of_line = lf
4 |
5 | max_line_length = 100
6 |
7 | indent_style = space
8 | indent_size = 4
9 | ij_continuation_indent_size = 8
10 | tab_width = 4
11 |
12 | trim_trailing_whitespace = true
13 | insert_final_newline = true
14 |
15 | [.editorconfig]
16 | ij_editorconfig_space_after_comma = false
17 | ij_editorconfig_space_before_comma = false
18 |
19 | [*.xml]
20 | ij_continuation_indent_size = 4
21 |
22 | [*.java]
23 | ij_java_class_count_to_use_import_on_demand = 2147483647
24 | ij_java_names_count_to_use_import_on_demand = 2147483647
25 | ij_java_use_single_class_imports = true
26 | ij_java_packages_to_use_import_on_demand = unset
27 | ij_java_layout_static_imports_separately = true
28 |
29 | ij_java_imports_layout = $android.**,$androidx.**,$com.**,$junit.**,$kotlin.**,$kotlinx.**,$net.**,$org.**,$java.**,$javax.**,$*,|,android.**,|,androidx.**,|,com.**,|,junit.**,|,kotlin.**,|,kotlinx.**,|,net.**,|,org.**,|,java.**,|,javax.**,|,*,|
30 |
31 | [*.{kt,kts}]
32 | ktlint_code_style = android_studio
33 | ktlint_function_naming_ignore_when_annotated_with = Composable
34 | ktlint_standard_function-expression-body = disabled
35 | ktlint_standard_function-signature = disabled
36 | ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 1
37 |
38 | ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
39 |
40 | # https://youtrack.jetbrains.com/issue/KTIJ-17907
41 | ij_continuation_indent_size = 4
42 |
43 | # Trailing comma
44 | ij_kotlin_allow_trailing_comma_on_call_site = true
45 | ij_kotlin_allow_trailing_comma = true
46 |
47 | # Blank lines
48 | ij_kotlin_keep_blank_lines_before_right_brace = 0
49 | ij_kotlin_keep_blank_lines_in_code = 1
50 | ij_kotlin_keep_blank_lines_in_declarations = 1
51 | ij_kotlin_blank_lines_after_class_header = 0
52 | ij_kotlin_blank_lines_around_block_when_branches = 1
53 | ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1
54 |
55 | # Misc wrapping
56 | ij_kotlin_assignment_wrap = on_every_item
57 | ij_kotlin_call_parameters_wrap = on_every_item
58 | ij_kotlin_enum_constants_wrap = on_every_item
59 | ij_kotlin_extends_list_wrap = on_every_item
60 | ij_kotlin_method_call_chain_wrap = on_every_item
61 | ij_kotlin_method_parameters_wrap = on_every_item
62 |
63 | # Annotation wrapping
64 | ij_kotlin_class_annotation_wrap = split_into_lines
65 | ij_kotlin_field_annotation_wrap = split_into_lines
66 | ij_kotlin_method_annotation_wrap = split_into_lines
67 | ij_kotlin_parameter_annotation_wrap = on_every_item
68 | ij_kotlin_variable_annotation_wrap = split_into_lines
69 |
70 | # Imports
71 | ij_kotlin_name_count_to_use_star_import = 2147483647
72 | ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
73 | ij_kotlin_packages_to_use_import_on_demand = unset
74 | ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,kotlinx.**,org.junit.**,^
75 |
76 | [*.{yml,yaml}]
77 | indent_size = 2
78 | tab_width = 2
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/common/SampleFilterChips.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.common
18 |
19 | import androidx.compose.foundation.horizontalScroll
20 | import androidx.compose.foundation.layout.Arrangement
21 | import androidx.compose.foundation.layout.PaddingValues
22 | import androidx.compose.foundation.layout.Row
23 | import androidx.compose.foundation.layout.fillMaxWidth
24 | import androidx.compose.foundation.layout.padding
25 | import androidx.compose.foundation.rememberScrollState
26 | import androidx.compose.foundation.shape.CircleShape
27 | import androidx.compose.material3.FilterChip
28 | import androidx.compose.material3.Text
29 | import androidx.compose.runtime.Composable
30 | import androidx.compose.runtime.getValue
31 | import androidx.compose.runtime.mutableStateOf
32 | import androidx.compose.runtime.remember
33 | import androidx.compose.runtime.setValue
34 | import androidx.compose.ui.Modifier
35 | import androidx.compose.ui.tooling.preview.Preview
36 | import androidx.compose.ui.unit.dp
37 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
38 |
39 | @Composable
40 | fun SampleFilterChips(
41 | modifier: Modifier = Modifier,
42 | contentPadding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 4.dp),
43 | ) {
44 | Row(
45 | modifier = modifier
46 | .fillMaxWidth()
47 | .horizontalScroll(rememberScrollState())
48 | .padding(contentPadding),
49 | horizontalArrangement = Arrangement.spacedBy(8.dp),
50 | ) {
51 | repeat(20) {
52 | FilterPill(
53 | index = it + 1,
54 | )
55 | }
56 | }
57 | }
58 |
59 | @Composable
60 | private fun FilterPill(
61 | index: Int,
62 | modifier: Modifier = Modifier,
63 | ) {
64 | var selected by remember { mutableStateOf(false) }
65 |
66 | FilterChip(
67 | modifier = modifier,
68 | selected = selected,
69 | onClick = { selected = !selected },
70 | label = { Text("Filter $index") },
71 | shape = CircleShape,
72 | )
73 | }
74 |
75 | @Preview(showBackground = true)
76 | @Composable
77 | private fun Preview() {
78 | ComposeCollapsingTopBarTheme {
79 | SampleFilterChips()
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/ComposeCollapsingTopBar/src/main/java/com/flaringapp/compose/topbar/nestedscroll/MultiNestedScrollConnection.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.nestedscroll
18 |
19 | import androidx.compose.ui.geometry.Offset
20 | import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
21 | import androidx.compose.ui.input.nestedscroll.NestedScrollSource
22 | import androidx.compose.ui.unit.Velocity
23 |
24 | /**
25 | * A [NestedScrollConnection] implementation that delegates nested scroll callbacks to [delegates]
26 | * while respecting their order: each subsequent delegate will receive available scroll minus
27 | * the amount consumed by all previous delegates.
28 | */
29 | class MultiNestedScrollConnection(
30 | private val delegates: List,
31 | ) : NestedScrollConnection {
32 |
33 | override fun onPreScroll(
34 | available: Offset,
35 | source: NestedScrollSource,
36 | ): Offset = delegates.fold(Offset.Zero) { delegatesConsumed, delegate ->
37 | delegate.onPreScroll(
38 | available = available - delegatesConsumed,
39 | source = source,
40 | ) + delegatesConsumed
41 | }
42 |
43 | override fun onPostScroll(
44 | consumed: Offset,
45 | available: Offset,
46 | source: NestedScrollSource,
47 | ): Offset = delegates.fold(Offset.Zero) { delegatesConsumed, delegate ->
48 | delegate.onPostScroll(
49 | consumed = consumed + delegatesConsumed,
50 | available = available - delegatesConsumed,
51 | source = source,
52 | ) + delegatesConsumed
53 | }
54 |
55 | override suspend fun onPreFling(
56 | available: Velocity,
57 | ): Velocity = delegates.fold(Velocity.Zero) { delegatesConsumed, delegate ->
58 | delegate.onPreFling(
59 | available = available - delegatesConsumed,
60 | ) + delegatesConsumed
61 | }
62 |
63 | override suspend fun onPostFling(
64 | consumed: Velocity,
65 | available: Velocity,
66 | ): Velocity = delegates.fold(Velocity.Zero) { delegatesConsumed, delegate ->
67 | delegate.onPostFling(
68 | consumed = consumed + delegatesConsumed,
69 | available = available - delegatesConsumed,
70 | ) + delegatesConsumed
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/common/SampleTopAppBar.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.common
18 |
19 | import androidx.compose.foundation.layout.WindowInsets
20 | import androidx.compose.material.icons.Icons
21 | import androidx.compose.material.icons.automirrored.rounded.ArrowBack
22 | import androidx.compose.material3.ExperimentalMaterial3Api
23 | import androidx.compose.material3.Icon
24 | import androidx.compose.material3.IconButton
25 | import androidx.compose.material3.Text
26 | import androidx.compose.material3.TopAppBar
27 | import androidx.compose.material3.TopAppBarDefaults
28 | import androidx.compose.runtime.Composable
29 | import androidx.compose.ui.Modifier
30 | import androidx.compose.ui.graphics.Color
31 | import androidx.compose.ui.tooling.preview.Preview
32 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
33 |
34 | @OptIn(ExperimentalMaterial3Api::class)
35 | @Composable
36 | fun SampleTopAppBar(
37 | title: String,
38 | onBack: () -> Unit,
39 | modifier: Modifier = Modifier,
40 | ignoreWindowInsets: Boolean = false,
41 | containerColor: Color = Color.Unspecified,
42 | contentColor: Color = Color.Unspecified,
43 | ) {
44 | val windowInsets = if (ignoreWindowInsets) {
45 | WindowInsets(0)
46 | } else {
47 | TopAppBarDefaults.windowInsets
48 | }
49 |
50 | TopAppBar(
51 | modifier = modifier,
52 | title = {
53 | Text(title)
54 | },
55 | navigationIcon = {
56 | IconButton(
57 | onClick = onBack,
58 | ) {
59 | Icon(
60 | imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
61 | contentDescription = "Back",
62 | )
63 | }
64 | },
65 | windowInsets = windowInsets,
66 | colors = TopAppBarDefaults.topAppBarColors(
67 | containerColor = containerColor,
68 | navigationIconContentColor = contentColor,
69 | titleContentColor = contentColor,
70 | ),
71 | )
72 | }
73 |
74 | @Preview
75 | @Composable
76 | private fun Preview() {
77 | ComposeCollapsingTopBarTheme {
78 | SampleTopAppBar(
79 | title = "Title",
80 | onBack = {},
81 | )
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/scaffold/ScaffoldVisibilityControls.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.scaffold
18 |
19 | import androidx.compose.foundation.layout.Arrangement
20 | import androidx.compose.foundation.layout.Column
21 | import androidx.compose.foundation.layout.Row
22 | import androidx.compose.foundation.layout.fillMaxWidth
23 | import androidx.compose.material3.Checkbox
24 | import androidx.compose.material3.Text
25 | import androidx.compose.runtime.Composable
26 | import androidx.compose.ui.Alignment
27 | import androidx.compose.ui.Modifier
28 | import androidx.compose.ui.tooling.preview.Preview
29 | import androidx.compose.ui.unit.dp
30 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
31 |
32 | @Composable
33 | fun ScaffoldVisibilityControls(
34 | doggoVisible: Boolean,
35 | columnVisible: Boolean,
36 | changeDoggoVisible: (Boolean) -> Unit,
37 | changeColumnVisible: (Boolean) -> Unit,
38 | modifier: Modifier = Modifier,
39 | ) {
40 | Row(
41 | modifier = modifier,
42 | horizontalArrangement = Arrangement.spacedBy(16.dp),
43 | ) {
44 | VisibilityControl(
45 | modifier = Modifier.weight(1f),
46 | label = "Doggo \uD83D\uDC36 visible",
47 | checked = doggoVisible,
48 | onCheckedChange = changeDoggoVisible,
49 | )
50 |
51 | VisibilityControl(
52 | modifier = Modifier.weight(1f),
53 | label = "Column visible",
54 | checked = columnVisible,
55 | onCheckedChange = changeColumnVisible,
56 | )
57 | }
58 | }
59 |
60 | @Composable
61 | private fun VisibilityControl(
62 | label: String,
63 | checked: Boolean,
64 | onCheckedChange: (Boolean) -> Unit,
65 | modifier: Modifier = Modifier,
66 | ) {
67 | Column(
68 | modifier = modifier,
69 | horizontalAlignment = Alignment.CenterHorizontally,
70 | ) {
71 | Checkbox(
72 | checked = checked,
73 | onCheckedChange = onCheckedChange,
74 | )
75 |
76 | Text(
77 | text = label,
78 | )
79 | }
80 | }
81 |
82 | @Preview(showBackground = true)
83 | @Composable
84 | private fun Preview() {
85 | ComposeCollapsingTopBarTheme {
86 | ScaffoldVisibilityControls(
87 | modifier = Modifier.fillMaxWidth(),
88 | doggoVisible = true,
89 | columnVisible = true,
90 | changeDoggoVisible = {},
91 | changeColumnVisible = {},
92 | )
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/advanced/ParallaxCollapsingSample.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.advanced
18 |
19 | import androidx.compose.foundation.layout.fillMaxSize
20 | import androidx.compose.material3.Surface
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.graphics.Color
24 | import androidx.compose.ui.tooling.preview.Preview
25 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffold
26 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldScrollMode
27 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSample
28 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSampleDogDefaults
29 | import com.flaringapp.compose.topbar.ui.samples.common.SampleContent
30 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopAppBar
31 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopBarImage
32 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
33 |
34 | object ParallaxCollapsingSample : CollapsingTopBarSample {
35 |
36 | override val name: String = "Parallax"
37 |
38 | @Composable
39 | override fun Content(onBack: () -> Unit) {
40 | ParallaxCollapsingSampleContent(onBack = onBack)
41 | }
42 | }
43 |
44 | @Composable
45 | fun ParallaxCollapsingSampleContent(
46 | onBack: () -> Unit,
47 | modifier: Modifier = Modifier,
48 | ) {
49 | Surface(
50 | modifier = modifier.fillMaxSize(),
51 | ) {
52 | CollapsingContent(
53 | onBack = onBack,
54 | )
55 | }
56 | }
57 |
58 | @Composable
59 | private fun CollapsingContent(
60 | onBack: () -> Unit,
61 | modifier: Modifier = Modifier,
62 | ) {
63 | CollapsingTopBarScaffold(
64 | modifier = modifier,
65 | scrollMode = CollapsingTopBarScaffoldScrollMode.collapse(expandAlways = false),
66 | topBar = {
67 | SampleTopBarImage(
68 | modifier = Modifier
69 | .parallax(0.25f),
70 | dog = CollapsingTopBarSampleDogDefaults.Advanced,
71 | )
72 |
73 | SampleTopAppBar(
74 | title = "Parallax",
75 | onBack = onBack,
76 | containerColor = Color.Transparent,
77 | )
78 | },
79 | body = {
80 | SampleContent()
81 | },
82 | )
83 | }
84 |
85 | @Preview
86 | @Composable
87 | private fun Preview() {
88 | ComposeCollapsingTopBarTheme {
89 | ParallaxCollapsingSampleContent(
90 | onBack = {},
91 | )
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/advanced/SnapCollapsingSample.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.advanced
18 |
19 | import androidx.compose.foundation.layout.fillMaxSize
20 | import androidx.compose.material3.Surface
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.graphics.Color
24 | import androidx.compose.ui.tooling.preview.Preview
25 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffold
26 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldScrollMode
27 | import com.flaringapp.compose.topbar.snap.rememberCollapsingTopBarSnapBehavior
28 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSample
29 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSampleDogDefaults
30 | import com.flaringapp.compose.topbar.ui.samples.common.SampleContent
31 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopAppBar
32 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopBarImage
33 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
34 |
35 | object SnapCollapsingSample : CollapsingTopBarSample {
36 |
37 | override val name: String = "Snapping"
38 |
39 | @Composable
40 | override fun Content(onBack: () -> Unit) {
41 | SnapCollapsingSampleContent(onBack = onBack)
42 | }
43 | }
44 |
45 | @Composable
46 | fun SnapCollapsingSampleContent(
47 | onBack: () -> Unit,
48 | modifier: Modifier = Modifier,
49 | ) {
50 | Surface(
51 | modifier = modifier.fillMaxSize(),
52 | ) {
53 | CollapsingContent(
54 | onBack = onBack,
55 | )
56 | }
57 | }
58 |
59 | @Composable
60 | private fun CollapsingContent(
61 | onBack: () -> Unit,
62 | modifier: Modifier = Modifier,
63 | ) {
64 | CollapsingTopBarScaffold(
65 | modifier = modifier,
66 | scrollMode = CollapsingTopBarScaffoldScrollMode.collapse(expandAlways = false),
67 | snapBehavior = rememberCollapsingTopBarSnapBehavior(),
68 | topBar = {
69 | SampleTopBarImage(
70 | dog = CollapsingTopBarSampleDogDefaults.Advanced,
71 | )
72 |
73 | SampleTopAppBar(
74 | title = "Snapping",
75 | onBack = onBack,
76 | containerColor = Color.Transparent,
77 | )
78 | },
79 | body = {
80 | SampleContent()
81 | },
82 | )
83 | }
84 |
85 | @Preview
86 | @Composable
87 | private fun Preview() {
88 | ComposeCollapsingTopBarTheme {
89 | SnapCollapsingSampleContent(
90 | onBack = {},
91 | )
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/basic/BasicScaffoldSample.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.basic
18 |
19 | import androidx.compose.material3.MaterialTheme
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.runtime.getValue
22 | import androidx.compose.runtime.mutableFloatStateOf
23 | import androidx.compose.runtime.remember
24 | import androidx.compose.runtime.setValue
25 | import androidx.compose.ui.Modifier
26 | import androidx.compose.ui.tooling.preview.Preview
27 | import androidx.compose.ui.util.lerp
28 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffold
29 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldScrollMode
30 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSampleDogDefaults
31 | import com.flaringapp.compose.topbar.ui.samples.common.SampleContent
32 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopAppBar
33 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopBarImage
34 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
35 |
36 | private const val SCRIM_START_FRACTION = 0.25f
37 |
38 | @Composable
39 | fun BasicScaffoldSampleContent(
40 | title: String,
41 | onBack: () -> Unit,
42 | scrollMode: CollapsingTopBarScaffoldScrollMode,
43 | modifier: Modifier = Modifier,
44 | ) {
45 | CollapsingTopBarScaffold(
46 | modifier = modifier,
47 | scrollMode = scrollMode,
48 | topBar = {
49 | var topBarColorProgress by remember { mutableFloatStateOf(1f) }
50 |
51 | SampleTopBarImage(
52 | modifier = Modifier
53 | .progress { _, itemProgress ->
54 | topBarColorProgress =
55 | itemProgress.coerceAtMost(SCRIM_START_FRACTION) / SCRIM_START_FRACTION
56 | },
57 | dog = CollapsingTopBarSampleDogDefaults.Basic,
58 | )
59 |
60 | SampleTopAppBar(
61 | title = title,
62 | onBack = onBack,
63 | containerColor = MaterialTheme.colorScheme.surface.copy(
64 | alpha = lerp(1f, 0f, topBarColorProgress),
65 | ),
66 | )
67 | },
68 | body = {
69 | SampleContent()
70 | },
71 | )
72 | }
73 |
74 | @Preview
75 | @Composable
76 | private fun Preview() {
77 | ComposeCollapsingTopBarTheme {
78 | BasicScaffoldSampleContent(
79 | title = "Basic Scaffold",
80 | onBack = {},
81 | scrollMode = CollapsingTopBarScaffoldScrollMode.collapse(expandAlways = false),
82 | )
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/CollapsingTopBarSampleGroups.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 Flaringapp
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.flaringapp.compose.topbar.ui.samples
18 |
19 | import com.flaringapp.compose.topbar.ui.samples.advanced.AppBarScrimSample
20 | import com.flaringapp.compose.topbar.ui.samples.advanced.AppBarShadowSample
21 | import com.flaringapp.compose.topbar.ui.samples.advanced.FloatingElementSample
22 | import com.flaringapp.compose.topbar.ui.samples.advanced.ManualCollapsingControlsSample
23 | import com.flaringapp.compose.topbar.ui.samples.advanced.ParallaxCollapsingSample
24 | import com.flaringapp.compose.topbar.ui.samples.advanced.SnapCollapsingSample
25 | import com.flaringapp.compose.topbar.ui.samples.basic.CollapsingExitExpandAlwaysSample
26 | import com.flaringapp.compose.topbar.ui.samples.basic.CollapsingExitExpandAtTopSample
27 | import com.flaringapp.compose.topbar.ui.samples.basic.CollapsingExpandAlwaysSample
28 | import com.flaringapp.compose.topbar.ui.samples.basic.CollapsingExpandAtTopSample
29 | import com.flaringapp.compose.topbar.ui.samples.basic.EnterAlwaysCollapsedSample
30 | import com.flaringapp.compose.topbar.ui.samples.column.AlternatelyCollapsibleColumnSample
31 | import com.flaringapp.compose.topbar.ui.samples.column.ColumnInStackSample
32 | import com.flaringapp.compose.topbar.ui.samples.column.ColumnMovingElementSample
33 | import com.flaringapp.compose.topbar.ui.samples.column.FullyCollapsibleColumnSample
34 | import com.flaringapp.compose.topbar.ui.samples.column.PartiallyCollapsibleColumnSample
35 | import com.flaringapp.compose.topbar.ui.samples.column.ReverseCollapsibleColumnSample
36 | import com.flaringapp.compose.topbar.ui.samples.scaffold.ScaffoldPlaygroundSample
37 |
38 | object CollapsingTopBarSampleGroups {
39 |
40 | val Basic: List
41 | get() = listOf(
42 | CollapsingExpandAtTopSample,
43 | CollapsingExpandAlwaysSample,
44 | CollapsingExitExpandAtTopSample,
45 | CollapsingExitExpandAlwaysSample,
46 | EnterAlwaysCollapsedSample,
47 | )
48 |
49 | val Column: List
50 | get() = listOf(
51 | FullyCollapsibleColumnSample,
52 | PartiallyCollapsibleColumnSample,
53 | ReverseCollapsibleColumnSample,
54 | AlternatelyCollapsibleColumnSample,
55 | ColumnInStackSample,
56 | ColumnMovingElementSample,
57 | )
58 |
59 | val Advanced: List
60 | get() = listOf(
61 | ParallaxCollapsingSample,
62 | SnapCollapsingSample,
63 | AppBarShadowSample,
64 | AppBarScrimSample,
65 | ManualCollapsingControlsSample,
66 | FloatingElementSample,
67 | )
68 |
69 | val Playground: List
70 | get() = listOf(
71 | ScaffoldPlaygroundSample,
72 | )
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/basic/CollapsingSample.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.basic
18 |
19 | import androidx.compose.foundation.layout.fillMaxSize
20 | import androidx.compose.material3.Surface
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.tooling.preview.Preview
24 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldScrollMode
25 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSample
26 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
27 |
28 | object CollapsingExpandAtTopSample : CollapsingTopBarSample {
29 |
30 | override val name: String = "Collapsing, expand at top"
31 |
32 | @Composable
33 | override fun Content(onBack: () -> Unit) {
34 | CollapsingExpandAtTopSampleContent(onBack = onBack)
35 | }
36 | }
37 |
38 | object CollapsingExpandAlwaysSample : CollapsingTopBarSample {
39 |
40 | override val name: String = "Collapsing, expand always"
41 |
42 | @Composable
43 | override fun Content(onBack: () -> Unit) {
44 | CollapsingExpandAlwaysSampleContent(onBack = onBack)
45 | }
46 | }
47 |
48 | @Composable
49 | fun CollapsingExpandAtTopSampleContent(
50 | onBack: () -> Unit,
51 | modifier: Modifier = Modifier,
52 | ) {
53 | CollapsingSampleContent(
54 | title = "Collapse / Expand At Top",
55 | modifier = modifier,
56 | onBack = onBack,
57 | expandAlways = false,
58 | )
59 | }
60 |
61 | @Composable
62 | fun CollapsingExpandAlwaysSampleContent(
63 | onBack: () -> Unit,
64 | modifier: Modifier = Modifier,
65 | ) {
66 | CollapsingSampleContent(
67 | modifier = modifier,
68 | title = "Collapse / Expand Always",
69 | onBack = onBack,
70 | expandAlways = true,
71 | )
72 | }
73 |
74 | @Composable
75 | private fun CollapsingSampleContent(
76 | title: String,
77 | onBack: () -> Unit,
78 | expandAlways: Boolean,
79 | modifier: Modifier = Modifier,
80 | ) {
81 | Surface(
82 | modifier = modifier.fillMaxSize(),
83 | ) {
84 | BasicScaffoldSampleContent(
85 | title = title,
86 | onBack = onBack,
87 | scrollMode = CollapsingTopBarScaffoldScrollMode.collapse(expandAlways = expandAlways),
88 | )
89 | }
90 | }
91 |
92 | @Preview
93 | @Composable
94 | private fun PreviewExpandAtTop() {
95 | ComposeCollapsingTopBarTheme {
96 | CollapsingExpandAtTopSampleContent(
97 | onBack = {},
98 | )
99 | }
100 | }
101 |
102 | @Preview
103 | @Composable
104 | private fun PreviewExpandAlways() {
105 | ComposeCollapsingTopBarTheme {
106 | CollapsingExpandAlwaysSampleContent(
107 | onBack = {},
108 | )
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/common/SampleContent.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.common
18 |
19 | import androidx.compose.foundation.background
20 | import androidx.compose.foundation.layout.Column
21 | import androidx.compose.foundation.layout.ColumnScope
22 | import androidx.compose.foundation.layout.fillMaxSize
23 | import androidx.compose.foundation.layout.fillMaxWidth
24 | import androidx.compose.foundation.layout.padding
25 | import androidx.compose.foundation.rememberScrollState
26 | import androidx.compose.foundation.verticalScroll
27 | import androidx.compose.material3.MaterialTheme
28 | import androidx.compose.material3.Text
29 | import androidx.compose.runtime.Composable
30 | import androidx.compose.ui.Alignment
31 | import androidx.compose.ui.Modifier
32 | import androidx.compose.ui.graphics.Color
33 | import androidx.compose.ui.graphics.lerp
34 | import androidx.compose.ui.text.style.TextAlign
35 | import androidx.compose.ui.tooling.preview.Preview
36 | import androidx.compose.ui.unit.dp
37 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
38 |
39 | @Composable
40 | inline fun SampleContent(
41 | modifier: Modifier = Modifier,
42 | before: ColumnScope.() -> Unit = {},
43 | ) {
44 | Column(
45 | modifier = modifier
46 | .fillMaxSize()
47 | .verticalScroll(rememberScrollState()),
48 | ) {
49 | before()
50 |
51 | repeat(50) {
52 | SampleContentItem(
53 | index = it,
54 | )
55 | }
56 | }
57 | }
58 |
59 | @Composable
60 | fun SampleContentItem(
61 | index: Int,
62 | modifier: Modifier = Modifier,
63 | ) {
64 | val backgroundColor = lerp(Color.LightGray, Color.DarkGray, index % 10 / 9f)
65 | val textColor = if (index % 10 >= 3) {
66 | Color.White
67 | } else {
68 | Color.Black
69 | }
70 |
71 | Text(
72 | modifier = modifier
73 | .fillMaxWidth()
74 | .background(backgroundColor)
75 | .padding(horizontal = 32.dp, vertical = 16.dp),
76 | text = "Item $index",
77 | color = textColor,
78 | textAlign = TextAlign.Center,
79 | style = MaterialTheme.typography.titleMedium,
80 | )
81 | }
82 |
83 | @Preview
84 | @Composable
85 | private fun Preview() {
86 | ComposeCollapsingTopBarTheme {
87 | SampleContent()
88 | }
89 | }
90 |
91 | @Preview
92 | @Composable
93 | private fun PreviewWithBefore() {
94 | ComposeCollapsingTopBarTheme {
95 | SampleContent {
96 | Text(
97 | modifier = Modifier
98 | .background(Color.Red)
99 | .padding(40.dp)
100 | .align(Alignment.CenterHorizontally),
101 | text = "Something before",
102 | )
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/basic/CollapsingExitSample.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.basic
18 |
19 | import androidx.compose.foundation.layout.fillMaxSize
20 | import androidx.compose.material3.Surface
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.tooling.preview.Preview
24 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldScrollMode
25 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSample
26 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
27 |
28 | object CollapsingExitExpandAtTopSample : CollapsingTopBarSample {
29 |
30 | override val name: String = "Collapsing with exit, expand at top"
31 |
32 | @Composable
33 | override fun Content(onBack: () -> Unit) {
34 | CollapsingExitExpandAtTopSampleContent(onBack = onBack)
35 | }
36 | }
37 |
38 | object CollapsingExitExpandAlwaysSample : CollapsingTopBarSample {
39 |
40 | override val name: String = "Collapsing with exit, expand always"
41 |
42 | @Composable
43 | override fun Content(onBack: () -> Unit) {
44 | CollapsingExitExpandAlwaysSampleContent(onBack = onBack)
45 | }
46 | }
47 |
48 | @Composable
49 | fun CollapsingExitExpandAtTopSampleContent(
50 | onBack: () -> Unit,
51 | modifier: Modifier = Modifier,
52 | ) {
53 | CollapsingExitSampleContent(
54 | modifier = modifier,
55 | title = "Collapse Exit / Expand At Top",
56 | onBack = onBack,
57 | expandAlways = false,
58 | )
59 | }
60 |
61 | @Composable
62 | fun CollapsingExitExpandAlwaysSampleContent(
63 | onBack: () -> Unit,
64 | modifier: Modifier = Modifier,
65 | ) {
66 | CollapsingExitSampleContent(
67 | modifier = modifier,
68 | title = "Collapse Exit / Expand Always",
69 | onBack = onBack,
70 | expandAlways = true,
71 | )
72 | }
73 |
74 | @Composable
75 | private fun CollapsingExitSampleContent(
76 | title: String,
77 | onBack: () -> Unit,
78 | expandAlways: Boolean,
79 | modifier: Modifier = Modifier,
80 | ) {
81 | Surface(
82 | modifier = modifier.fillMaxSize(),
83 | ) {
84 | BasicScaffoldSampleContent(
85 | title = title,
86 | onBack = onBack,
87 | scrollMode = CollapsingTopBarScaffoldScrollMode.collapseAndExit(
88 | expandAlways = expandAlways,
89 | ),
90 | )
91 | }
92 | }
93 |
94 | @Preview
95 | @Composable
96 | private fun PreviewExpandAtTop() {
97 | ComposeCollapsingTopBarTheme {
98 | CollapsingExitExpandAtTopSampleContent(
99 | onBack = {},
100 | )
101 | }
102 | }
103 |
104 | @Preview
105 | @Composable
106 | private fun PreviewExpandAlways() {
107 | ComposeCollapsingTopBarTheme {
108 | CollapsingExitExpandAlwaysSampleContent(
109 | onBack = {},
110 | )
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/gallery/SamplesGallery.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.gallery
18 |
19 | import androidx.compose.foundation.layout.Arrangement
20 | import androidx.compose.foundation.layout.Column
21 | import androidx.compose.foundation.layout.WindowInsets
22 | import androidx.compose.foundation.layout.fillMaxWidth
23 | import androidx.compose.foundation.layout.padding
24 | import androidx.compose.foundation.layout.windowInsetsPadding
25 | import androidx.compose.foundation.rememberScrollState
26 | import androidx.compose.foundation.verticalScroll
27 | import androidx.compose.material3.MaterialTheme
28 | import androidx.compose.material3.Text
29 | import androidx.compose.runtime.Composable
30 | import androidx.compose.runtime.mutableStateListOf
31 | import androidx.compose.runtime.remember
32 | import androidx.compose.runtime.snapshots.SnapshotStateList
33 | import androidx.compose.runtime.toMutableStateList
34 | import androidx.compose.ui.Modifier
35 | import androidx.compose.ui.text.style.TextAlign
36 | import androidx.compose.ui.tooling.preview.Preview
37 | import androidx.compose.ui.unit.dp
38 | import com.flaringapp.compose.topbar.screen
39 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSample
40 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSampleGroups
41 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
42 |
43 | @Composable
44 | fun SamplesGallery(
45 | groups: SnapshotStateList,
46 | onSampleSelect: (CollapsingTopBarSample) -> Unit,
47 | modifier: Modifier = Modifier,
48 | ) {
49 | Column(
50 | modifier = modifier
51 | .windowInsetsPadding(WindowInsets.screen)
52 | .verticalScroll(rememberScrollState())
53 | .padding(vertical = 24.dp),
54 | verticalArrangement = Arrangement.spacedBy(24.dp),
55 | ) {
56 | Text(
57 | modifier = Modifier
58 | .fillMaxWidth()
59 | .padding(horizontal = 32.dp),
60 | text = "Samples Gallery",
61 | color = MaterialTheme.colorScheme.primary,
62 | textAlign = TextAlign.Center,
63 | style = MaterialTheme.typography.headlineSmall,
64 | )
65 |
66 | groups.forEach { group ->
67 | SamplesGalleryRow(
68 | name = group.name,
69 | samples = group.samples,
70 | onSampleSelect = onSampleSelect,
71 | )
72 | }
73 | }
74 | }
75 |
76 | @Preview(showBackground = true)
77 | @Composable
78 | private fun Preview() {
79 | val groups = remember {
80 | mutableStateListOf(
81 | SamplesGalleryGroup(
82 | name = "First Group",
83 | samples = CollapsingTopBarSampleGroups.Basic.toMutableStateList(),
84 | ),
85 | SamplesGalleryGroup(
86 | name = "Second Group",
87 | samples = CollapsingTopBarSampleGroups.Column.toMutableStateList(),
88 | ),
89 | )
90 | }
91 |
92 | ComposeCollapsingTopBarTheme {
93 | SamplesGallery(
94 | groups = groups,
95 | onSampleSelect = {},
96 | )
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/advanced/AppBarShadowSample.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.advanced
18 |
19 | import androidx.compose.animation.core.animateDpAsState
20 | import androidx.compose.foundation.layout.fillMaxSize
21 | import androidx.compose.material3.Surface
22 | import androidx.compose.runtime.Composable
23 | import androidx.compose.runtime.getValue
24 | import androidx.compose.ui.Modifier
25 | import androidx.compose.ui.graphics.graphicsLayer
26 | import androidx.compose.ui.tooling.preview.Preview
27 | import androidx.compose.ui.unit.dp
28 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffold
29 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldScrollMode
30 | import com.flaringapp.compose.topbar.scaffold.rememberCollapsingTopBarScaffoldState
31 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSample
32 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSampleDogDefaults
33 | import com.flaringapp.compose.topbar.ui.samples.common.SampleContent
34 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopAppBar
35 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopBarImage
36 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
37 |
38 | object AppBarShadowSample : CollapsingTopBarSample {
39 |
40 | override val name: String = "App Bar Shadow"
41 |
42 | @Composable
43 | override fun Content(onBack: () -> Unit) {
44 | AppBarShadowSampleContent(onBack = onBack)
45 | }
46 | }
47 |
48 | @Composable
49 | fun AppBarShadowSampleContent(
50 | onBack: () -> Unit,
51 | modifier: Modifier = Modifier,
52 | ) {
53 | Surface(
54 | modifier = modifier.fillMaxSize(),
55 | ) {
56 | CollapsingContent(
57 | onBack = onBack,
58 | )
59 | }
60 | }
61 |
62 | @Composable
63 | private fun CollapsingContent(
64 | onBack: () -> Unit,
65 | modifier: Modifier = Modifier,
66 | ) {
67 | val state = rememberCollapsingTopBarScaffoldState()
68 |
69 | val topBarShadowElevation by animateDpAsState(
70 | label = "ShadowAnimation",
71 | targetValue = if (state.topBarState.isCollapsed) 12.dp else 0.dp,
72 | )
73 |
74 | CollapsingTopBarScaffold(
75 | modifier = modifier,
76 | state = state,
77 | scrollMode = CollapsingTopBarScaffoldScrollMode.collapse(expandAlways = false),
78 | topBarModifier = Modifier.graphicsLayer {
79 | shadowElevation = topBarShadowElevation.toPx()
80 | },
81 | topBar = {
82 | SampleTopBarImage(
83 | dog = CollapsingTopBarSampleDogDefaults.Advanced,
84 | )
85 |
86 | SampleTopAppBar(
87 | title = "App Bar Shadow",
88 | onBack = onBack,
89 | )
90 | },
91 | body = {
92 | SampleContent()
93 | },
94 | )
95 | }
96 |
97 | @Preview
98 | @Composable
99 | private fun Preview() {
100 | ComposeCollapsingTopBarTheme {
101 | AppBarShadowSampleContent(
102 | onBack = {},
103 | )
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/ComposeCollapsingTopBar/src/main/java/com/flaringapp/compose/topbar/nestedscroll/CollapsingTopBarNestedScrollStrategy.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.nestedscroll
18 |
19 | import androidx.compose.foundation.gestures.FlingBehavior
20 | import androidx.compose.foundation.gestures.ScrollableDefaults
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.runtime.Immutable
23 | import androidx.compose.runtime.remember
24 | import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
25 | import com.flaringapp.compose.topbar.snap.CollapsingTopBarNoSnapBehavior
26 | import com.flaringapp.compose.topbar.snap.CollapsingTopBarSnapBehavior
27 |
28 | /**
29 | * Create and remember [NestedScrollConnection] that delegates nested scrolling callbacks to the
30 | * list of handlers created with receiver [CollapsingTopBarNestedScrollStrategy.createHandlers].
31 | *
32 | * **The order of handlers matters.** The first handler receives all available scroll, while each
33 | * subsequent one gets less by the amount consumed by all previous handlers.
34 | *
35 | * @param state the target top bar state receiver strategy is going to be used with.
36 | * @param flingBehavior the fling behavior to be used for animating [state] fling.
37 | * @param snapBehavior the snap behavior to be used for animating [state] snap after fling.
38 | *
39 | * @return the instance of [NestedScrollConnection] that aggregates all handlers created with
40 | * [CollapsingTopBarNestedScrollStrategy.createHandlers].
41 | *
42 | * @see CollapsingTopBarNestedScrollStrategy
43 | * @see CollapsingTopBarNestedScrollHandler
44 | * @see MultiNestedScrollConnection
45 | */
46 | @Composable
47 | fun CollapsingTopBarNestedScrollStrategy.rememberNestedScrollConnection(
48 | state: STATE,
49 | flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
50 | snapBehavior: CollapsingTopBarSnapBehavior = CollapsingTopBarNoSnapBehavior,
51 | ): NestedScrollConnection {
52 | return remember(this, state, flingBehavior, snapBehavior) {
53 | MultiNestedScrollConnection(
54 | createHandlers(
55 | state = state,
56 | flingBehavior = flingBehavior,
57 | snapBehavior = snapBehavior,
58 | ),
59 | )
60 | }
61 | }
62 |
63 | /**
64 | * A strategy/factory for handling nested scrolling in scope of top bar collapse.
65 | * Responsible for creating sequence of [CollapsingTopBarNestedScrollHandler] that handle specific
66 | * pieces of scrolling logic.
67 | *
68 | * @see CollapsingTopBarNestedScrollHandler
69 | * @see MultiNestedScrollConnection
70 | */
71 | @Immutable
72 | interface CollapsingTopBarNestedScrollStrategy {
73 |
74 | /**
75 | * Creates a sequence of scroll handlers to process nested scroll.
76 | *
77 | * @param state the target top bar state receiver strategy is going to be used with.
78 | * @param flingBehavior the fling behavior to be used for animating [state] fling.
79 | * @param snapBehavior the snap behavior to be used for animating [state] snap after fling.
80 | *
81 | * @return list of nested scroll handlers.
82 | *
83 | * @see CollapsingTopBarNestedScrollHandler
84 | */
85 | fun createHandlers(
86 | state: STATE,
87 | flingBehavior: FlingBehavior,
88 | snapBehavior: CollapsingTopBarSnapBehavior,
89 | ): List
90 | }
91 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/column/FullyCollapsibleColumnSample.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.column
18 |
19 | import androidx.compose.foundation.layout.WindowInsets
20 | import androidx.compose.foundation.layout.WindowInsetsSides
21 | import androidx.compose.foundation.layout.fillMaxSize
22 | import androidx.compose.foundation.layout.only
23 | import androidx.compose.foundation.layout.padding
24 | import androidx.compose.foundation.shape.RoundedCornerShape
25 | import androidx.compose.material3.Surface
26 | import androidx.compose.runtime.Composable
27 | import androidx.compose.ui.Modifier
28 | import androidx.compose.ui.tooling.preview.Preview
29 | import androidx.compose.ui.unit.dp
30 | import com.flaringapp.compose.topbar.nestedcollapse.CollapsingTopBarColumn
31 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffold
32 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldScrollMode
33 | import com.flaringapp.compose.topbar.screen
34 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSample
35 | import com.flaringapp.compose.topbar.ui.samples.common.SampleContent
36 | import com.flaringapp.compose.topbar.ui.samples.common.SampleFilterChips
37 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopAppBar
38 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopBarBanner
39 | import com.flaringapp.compose.topbar.ui.samples.common.SampleVerticalFadingEdge
40 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
41 |
42 | object FullyCollapsibleColumnSample : CollapsingTopBarSample {
43 |
44 | override val name: String = "Fully Collapsible Column"
45 |
46 | @Composable
47 | override fun Content(onBack: () -> Unit) {
48 | FullyCollapsibleColumnSampleContent(onBack = onBack)
49 | }
50 | }
51 |
52 | @Composable
53 | fun FullyCollapsibleColumnSampleContent(
54 | onBack: () -> Unit,
55 | modifier: Modifier = Modifier,
56 | ) {
57 | Surface(
58 | modifier = modifier.fillMaxSize(),
59 | ) {
60 | CollapsingContent(
61 | onBack = onBack,
62 | )
63 | }
64 | }
65 |
66 | @Composable
67 | private fun CollapsingContent(
68 | onBack: () -> Unit,
69 | modifier: Modifier = Modifier,
70 | ) {
71 | CollapsingTopBarScaffold(
72 | modifier = modifier,
73 | scrollMode = CollapsingTopBarScaffoldScrollMode.collapse(expandAlways = false),
74 | topBar = { topBarState ->
75 | CollapsingTopBarColumn(topBarState) {
76 | SampleTopBarBanner(
77 | shape = RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp),
78 | windowInsets = WindowInsets.screen.only(WindowInsetsSides.Top),
79 | )
80 |
81 | SampleTopAppBar(
82 | modifier = Modifier.padding(top = 8.dp),
83 | title = "Fully Collapsible Column",
84 | onBack = onBack,
85 | ignoreWindowInsets = true,
86 | )
87 |
88 | SampleVerticalFadingEdge()
89 | SampleFilterChips()
90 | }
91 | },
92 | body = {
93 | SampleContent()
94 | },
95 | )
96 | }
97 |
98 | @Preview
99 | @Composable
100 | private fun Preview() {
101 | ComposeCollapsingTopBarTheme {
102 | FullyCollapsibleColumnSampleContent(
103 | onBack = {},
104 | )
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/ComposeCollapsingTopBar/src/main/java/com/flaringapp/compose/topbar/snap/CollapsingTopBarSnapBehavior.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.snap
18 |
19 | import androidx.compose.animation.core.AnimationSpec
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.runtime.Immutable
22 | import androidx.compose.runtime.annotation.RememberInComposition
23 | import androidx.compose.runtime.remember
24 | import com.flaringapp.compose.topbar.CollapsingTopBarControls.Companion.DefaultAnimationSpec
25 |
26 | /**
27 | * Defines the logic of snap animation.
28 | *
29 | * When nested scroll fling has ended, [snap] is called to perform snapping animation with
30 | * [CollapsingTopBarSnapScope] receiver.
31 | */
32 | @Immutable
33 | interface CollapsingTopBarSnapBehavior {
34 |
35 | /**
36 | * Performs snapping animation with [CollapsingTopBarSnapScope] receiver.
37 | *
38 | * @param wasMovingUp whether a fling motion that preceded snapping was directed upwards
39 | * (with negative velocity) or not.
40 | */
41 | suspend fun CollapsingTopBarSnapScope.snap(wasMovingUp: Boolean)
42 | }
43 |
44 | /**
45 | * A simple implementation of [CollapsingTopBarSnapBehavior] that **does not** perform snapping.
46 | */
47 | object CollapsingTopBarNoSnapBehavior : CollapsingTopBarSnapBehavior {
48 |
49 | override suspend fun CollapsingTopBarSnapScope.snap(wasMovingUp: Boolean) = Unit
50 | }
51 |
52 | /**
53 | * Performs snapping based on current [CollapsingTopBarSnapScope] collapse progress received in
54 | * lambda [CollapsingTopBarSnapScope.snapWithProgress]. Uses [threshold] as a bound to determine
55 | * in which direction to snap.
56 | *
57 | * @param threshold the fraction of collapse progress, a bound to define in which direction to snap.
58 | * If current collapse progress is larger than this value, then expand snapping is performed;
59 | * collapse snapping is performed otherwise.
60 | * @param animationSpec the animation spec of snap animation.
61 | */
62 | class CollapsingTopBarThresholdSnapBehavior @RememberInComposition constructor(
63 | private val threshold: Float = 0.5f,
64 | private val animationSpec: AnimationSpec = DefaultAnimationSpec,
65 | ) : CollapsingTopBarSnapBehavior {
66 |
67 | override suspend fun CollapsingTopBarSnapScope.snap(wasMovingUp: Boolean) {
68 | snapWithProgress(wasMovingUp) { progress ->
69 | if (progress >= threshold) {
70 | expand(animationSpec)
71 | } else {
72 | collapse(animationSpec)
73 | }
74 | }
75 | }
76 | }
77 |
78 | /**
79 | * Create and remember [CollapsingTopBarThresholdSnapBehavior] which performs snapping based on
80 | * current collapse progress and specified [threshold].
81 | *
82 | * @param threshold the fraction of collapse progress, a bound to define in which direction to snap.
83 | * If current collapse progress is larger than this value, then expand snapping is performed;
84 | * collapse snapping is performed otherwise.
85 | * @param animationSpec the animation spec of snap animation.
86 | *
87 | * @see CollapsingTopBarThresholdSnapBehavior
88 | */
89 | @Composable
90 | fun rememberCollapsingTopBarSnapBehavior(
91 | threshold: Float = 0.5f,
92 | animationSpec: AnimationSpec = DefaultAnimationSpec,
93 | ): CollapsingTopBarSnapBehavior {
94 | return remember(threshold) {
95 | CollapsingTopBarThresholdSnapBehavior(
96 | threshold = threshold,
97 | animationSpec = animationSpec,
98 | )
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/ComposeCollapsingTopBar/src/main/java/com/flaringapp/compose/topbar/CollapsingTopBarControls.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar
18 |
19 | import androidx.compose.animation.core.AnimationSpec
20 | import androidx.compose.animation.core.AnimationState
21 | import androidx.compose.animation.core.Spring
22 | import androidx.compose.animation.core.VisibilityThreshold
23 | import androidx.compose.animation.core.animateTo
24 | import androidx.compose.animation.core.spring
25 | import androidx.compose.foundation.gestures.ScrollScope
26 | import androidx.compose.foundation.gestures.ScrollableState
27 | import androidx.compose.runtime.Stable
28 | import androidx.compose.ui.geometry.Offset
29 |
30 | /**
31 | * A functional contract of any top bar state that manages collapsing behavior. Allows to
32 | * [collapse] and [expand], as well as provides animation utility methods.
33 | */
34 | @Stable
35 | interface CollapsingTopBarControls {
36 |
37 | companion object {
38 |
39 | val DefaultAnimationSpec: AnimationSpec = spring(
40 | stiffness = Spring.StiffnessMediumLow,
41 | visibilityThreshold = Offset.VisibilityThreshold.y,
42 | )
43 | }
44 |
45 | /**
46 | * Animates top bar state collapsing height to its maximum value, i.e. expands.
47 | *
48 | * @param animationSpec the animation spec of expand animation.
49 | */
50 | suspend fun expand(
51 | animationSpec: AnimationSpec = DefaultAnimationSpec,
52 | )
53 |
54 | /**
55 | * Animates top bar state collapsing height to its minimum value, i.e. collapses.
56 | *
57 | * @param animationSpec the animation spec of collapse animation.
58 | */
59 | suspend fun collapse(
60 | animationSpec: AnimationSpec = DefaultAnimationSpec,
61 | )
62 |
63 | /**
64 | * An utility method to perform collapsing top bar height animation with [ScrollableState].
65 | * Useful for state implementations that also implement [ScrollableState].
66 | *
67 | * @param currentHeight the current height of top bar.
68 | * @param targetHeight the target height of top bar to animate to.
69 | * @param animationSpec the animation spec of height animation.
70 | */
71 | suspend fun ScrollableState.animateHeightTo(
72 | currentHeight: Float,
73 | targetHeight: Float,
74 | animationSpec: AnimationSpec,
75 | ) {
76 | scroll {
77 | animateHeightTo(
78 | currentHeight = currentHeight,
79 | targetHeight = targetHeight,
80 | animationSpec = animationSpec,
81 | )
82 | }
83 | }
84 |
85 | /**
86 | * An utility method to perform collapsing top bar height animation on [ScrollScope].
87 | *
88 | * @param currentHeight the current height of top bar.
89 | * @param targetHeight the target height of top bar to animate to.
90 | * @param animationSpec the animation spec of height animation.
91 | */
92 | suspend fun ScrollScope.animateHeightTo(
93 | currentHeight: Float,
94 | targetHeight: Float,
95 | animationSpec: AnimationSpec,
96 | ) {
97 | if (currentHeight == targetHeight) return
98 |
99 | val animation = AnimationState(currentHeight)
100 | var previousAnimatedValue = animation.value
101 |
102 | animation.animateTo(
103 | targetValue = targetHeight,
104 | animationSpec = animationSpec,
105 | ) {
106 | scrollBy(value - previousAnimatedValue)
107 | previousAnimatedValue = value
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/advanced/AppBarScrimSample.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.advanced
18 |
19 | import androidx.compose.foundation.layout.fillMaxSize
20 | import androidx.compose.material3.MaterialTheme
21 | import androidx.compose.material3.Surface
22 | import androidx.compose.runtime.Composable
23 | import androidx.compose.runtime.derivedStateOf
24 | import androidx.compose.runtime.getValue
25 | import androidx.compose.runtime.remember
26 | import androidx.compose.ui.Modifier
27 | import androidx.compose.ui.graphics.lerp
28 | import androidx.compose.ui.tooling.preview.Preview
29 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffold
30 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldScrollMode
31 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSample
32 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSampleDogDefaults
33 | import com.flaringapp.compose.topbar.ui.samples.common.SampleContent
34 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopAppBar
35 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopBarImage
36 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
37 | import androidx.compose.ui.util.lerp as primitiveLerp
38 |
39 | private const val SCRIM_START_FRACTION = 0.5f
40 |
41 | object AppBarScrimSample : CollapsingTopBarSample {
42 |
43 | override val name: String = "App Bar Scrim"
44 |
45 | @Composable
46 | override fun Content(onBack: () -> Unit) {
47 | AppBarScrimSampleContent(onBack = onBack)
48 | }
49 | }
50 |
51 | @Composable
52 | fun AppBarScrimSampleContent(
53 | onBack: () -> Unit,
54 | modifier: Modifier = Modifier,
55 | ) {
56 | Surface(
57 | modifier = modifier.fillMaxSize(),
58 | ) {
59 | CollapsingContent(
60 | onBack = onBack,
61 | )
62 | }
63 | }
64 |
65 | @Composable
66 | private fun CollapsingContent(
67 | onBack: () -> Unit,
68 | modifier: Modifier = Modifier,
69 | ) {
70 | CollapsingTopBarScaffold(
71 | modifier = modifier,
72 | scrollMode = CollapsingTopBarScaffoldScrollMode.collapse(expandAlways = false),
73 | topBar = { topBarState ->
74 | val topBarColorProgress by remember {
75 | derivedStateOf {
76 | val progress = topBarState.layoutInfo.collapseProgress
77 | // Treat 0 <-> >=SCRIM_START_FRACTION progress as 0 <-> 1
78 | progress.coerceAtMost(SCRIM_START_FRACTION) / SCRIM_START_FRACTION
79 | }
80 | }
81 |
82 | SampleTopBarImage(
83 | dog = CollapsingTopBarSampleDogDefaults.Advanced,
84 | )
85 |
86 | SampleTopAppBar(
87 | title = "App Bar Scrim",
88 | onBack = onBack,
89 | containerColor = MaterialTheme.colorScheme.primaryContainer.copy(
90 | alpha = primitiveLerp(1f, 0f, topBarColorProgress),
91 | ),
92 | contentColor = lerp(
93 | start = MaterialTheme.colorScheme.onSurface,
94 | stop = MaterialTheme.colorScheme.onPrimaryContainer,
95 | fraction = topBarColorProgress,
96 | ),
97 | )
98 | },
99 | body = {
100 | SampleContent()
101 | },
102 | )
103 | }
104 |
105 | @Preview
106 | @Composable
107 | private fun Preview() {
108 | ComposeCollapsingTopBarTheme {
109 | AppBarScrimSampleContent(
110 | onBack = {},
111 | )
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/scaffold/ScaffoldColumnDirectionControls.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.scaffold
18 |
19 | import androidx.compose.foundation.layout.IntrinsicSize
20 | import androidx.compose.foundation.layout.fillMaxHeight
21 | import androidx.compose.foundation.layout.fillMaxWidth
22 | import androidx.compose.foundation.layout.height
23 | import androidx.compose.material3.MaterialTheme
24 | import androidx.compose.material3.SegmentedButton
25 | import androidx.compose.material3.SingleChoiceSegmentedButtonRow
26 | import androidx.compose.material3.SingleChoiceSegmentedButtonRowScope
27 | import androidx.compose.material3.Text
28 | import androidx.compose.runtime.Composable
29 | import androidx.compose.runtime.Immutable
30 | import androidx.compose.runtime.mutableStateListOf
31 | import androidx.compose.runtime.remember
32 | import androidx.compose.ui.Modifier
33 | import androidx.compose.ui.graphics.RectangleShape
34 | import androidx.compose.ui.text.style.TextAlign
35 | import androidx.compose.ui.tooling.preview.Preview
36 | import com.flaringapp.compose.topbar.nestedcollapse.CollapsingTopBarColumnDirection
37 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
38 |
39 | @Composable
40 | fun ScaffoldColumnDirectionControls(
41 | selectedMode: ScaffoldColumnDirectionMode,
42 | selectMode: (ScaffoldColumnDirectionMode) -> Unit,
43 | modifier: Modifier = Modifier,
44 | ) {
45 | val modes = remember {
46 | mutableStateListOf(
47 | ScaffoldColumnDirectionMode.BottomUp,
48 | ScaffoldColumnDirectionMode.TopToBottom,
49 | )
50 | }
51 |
52 | SingleChoiceSegmentedButtonRow(
53 | modifier = modifier.height(IntrinsicSize.Max),
54 | ) {
55 | modes.forEach { mode ->
56 | CollapseModeButton(
57 | modifier = Modifier.fillMaxHeight(),
58 | mode = mode,
59 | selected = mode == selectedMode,
60 | select = { selectMode(mode) },
61 | )
62 | }
63 | }
64 | }
65 |
66 | @Composable
67 | private fun SingleChoiceSegmentedButtonRowScope.CollapseModeButton(
68 | mode: ScaffoldColumnDirectionMode,
69 | selected: Boolean,
70 | select: () -> Unit,
71 | modifier: Modifier = Modifier,
72 | ) {
73 | SegmentedButton(
74 | modifier = modifier,
75 | selected = selected,
76 | onClick = select,
77 | shape = RectangleShape,
78 | ) {
79 | Text(
80 | text = mode.name,
81 | textAlign = TextAlign.Center,
82 | style = MaterialTheme.typography.labelMedium,
83 | )
84 | }
85 | }
86 |
87 | @Immutable
88 | sealed class ScaffoldColumnDirectionMode {
89 |
90 | abstract val name: String
91 |
92 | abstract fun provideColumnDirection(): CollapsingTopBarColumnDirection
93 |
94 | data object BottomUp : ScaffoldColumnDirectionMode() {
95 |
96 | override val name: String
97 | get() = "Bottom up"
98 |
99 | override fun provideColumnDirection(): CollapsingTopBarColumnDirection {
100 | return CollapsingTopBarColumnDirection.BottomUp
101 | }
102 | }
103 |
104 | data object TopToBottom : ScaffoldColumnDirectionMode() {
105 |
106 | override val name: String
107 | get() = "Top to bottom"
108 |
109 | override fun provideColumnDirection(): CollapsingTopBarColumnDirection {
110 | return CollapsingTopBarColumnDirection.TopToBottom
111 | }
112 | }
113 | }
114 |
115 | @Preview(showBackground = true)
116 | @Composable
117 | private fun Preview() {
118 | ComposeCollapsingTopBarTheme {
119 | ScaffoldColumnDirectionControls(
120 | modifier = Modifier.fillMaxWidth(),
121 | selectedMode = ScaffoldColumnDirectionMode.BottomUp,
122 | selectMode = {},
123 | )
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/gallery/SamplesGalleryRow.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.gallery
18 |
19 | import androidx.compose.foundation.horizontalScroll
20 | import androidx.compose.foundation.layout.Arrangement
21 | import androidx.compose.foundation.layout.Box
22 | import androidx.compose.foundation.layout.Column
23 | import androidx.compose.foundation.layout.IntrinsicSize
24 | import androidx.compose.foundation.layout.Row
25 | import androidx.compose.foundation.layout.fillMaxWidth
26 | import androidx.compose.foundation.layout.height
27 | import androidx.compose.foundation.layout.heightIn
28 | import androidx.compose.foundation.layout.padding
29 | import androidx.compose.foundation.layout.width
30 | import androidx.compose.foundation.rememberScrollState
31 | import androidx.compose.material3.MaterialTheme
32 | import androidx.compose.material3.Surface
33 | import androidx.compose.material3.Text
34 | import androidx.compose.runtime.Composable
35 | import androidx.compose.runtime.remember
36 | import androidx.compose.runtime.snapshots.SnapshotStateList
37 | import androidx.compose.runtime.toMutableStateList
38 | import androidx.compose.ui.Alignment
39 | import androidx.compose.ui.Modifier
40 | import androidx.compose.ui.text.style.TextAlign
41 | import androidx.compose.ui.tooling.preview.Preview
42 | import androidx.compose.ui.unit.Dp
43 | import androidx.compose.ui.unit.dp
44 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSample
45 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSampleGroups
46 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
47 |
48 | @Composable
49 | fun SamplesGalleryRow(
50 | name: String,
51 | samples: SnapshotStateList,
52 | onSampleSelect: (CollapsingTopBarSample) -> Unit,
53 | modifier: Modifier = Modifier,
54 | horizontalPadding: Dp = 24.dp,
55 | ) {
56 | Column(
57 | modifier = modifier,
58 | ) {
59 | Text(
60 | modifier = Modifier.padding(horizontal = horizontalPadding),
61 | text = name,
62 | style = MaterialTheme.typography.titleMedium,
63 | )
64 |
65 | Row(
66 | modifier = Modifier
67 | .padding(top = 8.dp)
68 | .horizontalScroll(rememberScrollState())
69 | .fillMaxWidth()
70 | .height(IntrinsicSize.Max)
71 | .padding(horizontal = horizontalPadding),
72 | horizontalArrangement = Arrangement.spacedBy(8.dp),
73 | ) {
74 | samples.forEach { sample ->
75 | SampleCard(
76 | name = sample.name,
77 | onClick = { onSampleSelect(sample) },
78 | )
79 | }
80 | }
81 | }
82 | }
83 |
84 | @Composable
85 | private fun SampleCard(
86 | name: String,
87 | onClick: () -> Unit,
88 | modifier: Modifier = Modifier,
89 | ) {
90 | Surface(
91 | modifier = modifier
92 | .width(112.dp)
93 | .heightIn(min = 140.dp),
94 | color = MaterialTheme.colorScheme.primaryContainer,
95 | shape = MaterialTheme.shapes.large,
96 | onClick = onClick,
97 | ) {
98 | Box(
99 | contentAlignment = Alignment.Center,
100 | ) {
101 | Text(
102 | modifier = Modifier.padding(16.dp),
103 | text = name,
104 | textAlign = TextAlign.Center,
105 | style = MaterialTheme.typography.bodyMedium,
106 | )
107 | }
108 | }
109 | }
110 |
111 | @Preview(showBackground = true)
112 | @Composable
113 | private fun Preview() {
114 | val samples = remember {
115 | CollapsingTopBarSampleGroups.Basic.toMutableStateList()
116 | }
117 |
118 | ComposeCollapsingTopBarTheme {
119 | SamplesGalleryRow(
120 | name = "Preview Gallery Row",
121 | samples = samples,
122 | onSampleSelect = {},
123 | )
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/MainActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar
18 |
19 | import android.os.Bundle
20 | import androidx.activity.ComponentActivity
21 | import androidx.activity.compose.BackHandler
22 | import androidx.activity.compose.setContent
23 | import androidx.activity.enableEdgeToEdge
24 | import androidx.compose.animation.AnimatedContent
25 | import androidx.compose.animation.fadeIn
26 | import androidx.compose.animation.fadeOut
27 | import androidx.compose.animation.scaleIn
28 | import androidx.compose.animation.scaleOut
29 | import androidx.compose.animation.togetherWith
30 | import androidx.compose.foundation.layout.fillMaxSize
31 | import androidx.compose.runtime.Composable
32 | import androidx.compose.runtime.getValue
33 | import androidx.compose.runtime.mutableStateListOf
34 | import androidx.compose.runtime.mutableStateOf
35 | import androidx.compose.runtime.remember
36 | import androidx.compose.runtime.setValue
37 | import androidx.compose.runtime.snapshots.SnapshotStateList
38 | import androidx.compose.runtime.toMutableStateList
39 | import androidx.compose.ui.Modifier
40 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSample
41 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSampleGroups
42 | import com.flaringapp.compose.topbar.ui.samples.gallery.SamplesGallery
43 | import com.flaringapp.compose.topbar.ui.samples.gallery.SamplesGalleryGroup
44 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
45 |
46 | class MainActivity : ComponentActivity() {
47 | override fun onCreate(savedInstanceState: Bundle?) {
48 | super.onCreate(savedInstanceState)
49 | enableEdgeToEdge()
50 | setContent {
51 | ComposeCollapsingTopBarTheme {
52 | SamplesNavigation()
53 | }
54 | }
55 | }
56 | }
57 |
58 | @Composable
59 | private fun SamplesNavigation() {
60 | var selectedSample: CollapsingTopBarSample? by remember {
61 | mutableStateOf(null)
62 | }
63 |
64 | BackHandler(
65 | enabled = selectedSample != null,
66 | ) {
67 | selectedSample = null
68 | }
69 |
70 | AnimatedContent(
71 | modifier = Modifier.fillMaxSize(),
72 | label = "SampleContentAnimation",
73 | targetState = selectedSample,
74 | transitionSpec = {
75 | val isOpeningSample = targetState != null
76 | if (isOpeningSample) {
77 | fadeIn() + scaleIn(initialScale = 1.1f) togetherWith fadeOut() using null
78 | } else {
79 | fadeIn() togetherWith fadeOut() + scaleOut(targetScale = 1.1f) using null
80 | }
81 | },
82 | ) { currentSelectedSample ->
83 | if (currentSelectedSample != null) {
84 | currentSelectedSample.Content(
85 | onBack = { selectedSample = null },
86 | )
87 | return@AnimatedContent
88 | }
89 |
90 | SamplesGallery(
91 | groups = rememberSampleGroups(),
92 | onSampleSelect = { selectedSample = it },
93 | )
94 | }
95 | }
96 |
97 | @Composable
98 | private fun rememberSampleGroups(): SnapshotStateList {
99 | return remember {
100 | mutableStateListOf(
101 | SamplesGalleryGroup(
102 | name = "Basic Samples",
103 | samples = CollapsingTopBarSampleGroups.Basic.toMutableStateList(),
104 | ),
105 | SamplesGalleryGroup(
106 | name = "Column Samples",
107 | samples = CollapsingTopBarSampleGroups.Column.toMutableStateList(),
108 | ),
109 | SamplesGalleryGroup(
110 | name = "Advanced Samples",
111 | samples = CollapsingTopBarSampleGroups.Advanced.toMutableStateList(),
112 | ),
113 | SamplesGalleryGroup(
114 | name = "Playground Samples",
115 | samples = CollapsingTopBarSampleGroups.Playground.toMutableStateList(),
116 | ),
117 | )
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/advanced/FloatingElementSample.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.advanced
18 |
19 | import androidx.compose.foundation.background
20 | import androidx.compose.foundation.layout.Box
21 | import androidx.compose.foundation.layout.fillMaxSize
22 | import androidx.compose.foundation.layout.size
23 | import androidx.compose.foundation.shape.CircleShape
24 | import androidx.compose.material3.Surface
25 | import androidx.compose.runtime.Composable
26 | import androidx.compose.ui.Modifier
27 | import androidx.compose.ui.graphics.Color
28 | import androidx.compose.ui.layout.layout
29 | import androidx.compose.ui.tooling.preview.Preview
30 | import androidx.compose.ui.unit.dp
31 | import androidx.compose.ui.util.lerp
32 | import com.flaringapp.compose.topbar.CollapsingTopBarState
33 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffold
34 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldScrollMode
35 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSample
36 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSampleDogDefaults
37 | import com.flaringapp.compose.topbar.ui.samples.common.SampleContent
38 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopAppBar
39 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopBarImage
40 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
41 |
42 | object FloatingElementSample : CollapsingTopBarSample {
43 |
44 | override val name: String = "Floating Element"
45 |
46 | @Composable
47 | override fun Content(onBack: () -> Unit) {
48 | FloatingElementSampleContent(onBack = onBack)
49 | }
50 | }
51 |
52 | @Composable
53 | fun FloatingElementSampleContent(
54 | onBack: () -> Unit,
55 | modifier: Modifier = Modifier,
56 | ) {
57 | Surface(
58 | modifier = modifier.fillMaxSize(),
59 | ) {
60 | CollapsingContent(
61 | onBack = onBack,
62 | )
63 | }
64 | }
65 |
66 | @Composable
67 | private fun CollapsingContent(
68 | onBack: () -> Unit,
69 | modifier: Modifier = Modifier,
70 | ) {
71 | CollapsingTopBarScaffold(
72 | modifier = modifier,
73 | scrollMode = CollapsingTopBarScaffoldScrollMode.collapseAndExit(expandAlways = false),
74 | topBar = { topBarState ->
75 | SampleTopBarImage(
76 | dog = CollapsingTopBarSampleDogDefaults.Advanced,
77 | )
78 |
79 | SampleTopAppBar(
80 | title = "Floating Element",
81 | onBack = onBack,
82 | containerColor = Color.Transparent,
83 | )
84 |
85 | SlidingDot(
86 | modifier = Modifier.floating(),
87 | state = topBarState,
88 | )
89 | },
90 | body = {
91 | SampleContent()
92 | },
93 | )
94 | }
95 |
96 | @Composable
97 | private fun SlidingDot(
98 | state: CollapsingTopBarState,
99 | modifier: Modifier = Modifier,
100 | ) {
101 | Box(
102 | modifier = modifier
103 | .layout { measurable, constraints ->
104 | val placeable = measurable.measure(constraints)
105 | layout(placeable.width, placeable.height) {
106 | val layoutInfo = state.layoutInfo
107 | val x = lerp(
108 | start = constraints.maxWidth - placeable.width - 16.dp.roundToPx(),
109 | stop = 16.dp.roundToPx(),
110 | fraction = layoutInfo.collapseProgress,
111 | )
112 | val y = layoutInfo.height.toInt() - placeable.height - 16.dp.roundToPx()
113 | placeable.place(x, y)
114 | }
115 | }
116 | .size(40.dp)
117 | .background(color = Color.Red, shape = CircleShape),
118 | )
119 | }
120 |
121 | @Preview
122 | @Composable
123 | private fun Preview() {
124 | ComposeCollapsingTopBarTheme {
125 | FloatingElementSampleContent(
126 | onBack = {},
127 | )
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/advanced/ManualCollapsingControlsSample.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.advanced
18 |
19 | import androidx.compose.foundation.background
20 | import androidx.compose.foundation.layout.Arrangement
21 | import androidx.compose.foundation.layout.Row
22 | import androidx.compose.foundation.layout.fillMaxSize
23 | import androidx.compose.foundation.layout.fillMaxWidth
24 | import androidx.compose.material3.Button
25 | import androidx.compose.material3.Surface
26 | import androidx.compose.material3.Text
27 | import androidx.compose.runtime.Composable
28 | import androidx.compose.runtime.getValue
29 | import androidx.compose.runtime.setValue
30 | import androidx.compose.ui.Alignment
31 | import androidx.compose.ui.Modifier
32 | import androidx.compose.ui.graphics.Color
33 | import androidx.compose.ui.tooling.preview.Preview
34 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffold
35 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldScrollMode
36 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldState
37 | import com.flaringapp.compose.topbar.scaffold.rememberCollapsingTopBarScaffoldState
38 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSample
39 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSampleDogDefaults
40 | import com.flaringapp.compose.topbar.ui.samples.common.SampleContent
41 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopAppBar
42 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopBarImage
43 | import com.flaringapp.compose.topbar.ui.samples.common.rememberSampleExpandRequestHandler
44 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
45 |
46 | object ManualCollapsingControlsSample : CollapsingTopBarSample {
47 |
48 | override val name: String = "Manual Collapsing"
49 |
50 | @Composable
51 | override fun Content(onBack: () -> Unit) {
52 | ManualCollapsingControlsSampleContent(onBack = onBack)
53 | }
54 | }
55 |
56 | @Composable
57 | fun ManualCollapsingControlsSampleContent(
58 | onBack: () -> Unit,
59 | modifier: Modifier = Modifier,
60 | ) {
61 | Surface(
62 | modifier = modifier.fillMaxSize(),
63 | ) {
64 | CollapsingContent(
65 | onBack = onBack,
66 | )
67 | }
68 | }
69 |
70 | @Composable
71 | private fun CollapsingContent(
72 | onBack: () -> Unit,
73 | modifier: Modifier = Modifier,
74 | ) {
75 | val state = rememberCollapsingTopBarScaffoldState()
76 |
77 | CollapsingTopBarScaffold(
78 | modifier = modifier,
79 | state = state,
80 | scrollMode = CollapsingTopBarScaffoldScrollMode.collapse(expandAlways = false),
81 | topBar = {
82 | SampleTopBarImage(
83 | dog = CollapsingTopBarSampleDogDefaults.Advanced,
84 | )
85 |
86 | SampleTopAppBar(
87 | title = "Manual Collapsing",
88 | onBack = onBack,
89 | containerColor = Color.Transparent,
90 | )
91 | },
92 | body = {
93 | SampleContent {
94 | TopBarControls(
95 | state = state,
96 | )
97 | }
98 | },
99 | )
100 | }
101 |
102 | @Composable
103 | private fun TopBarControls(
104 | state: CollapsingTopBarScaffoldState,
105 | modifier: Modifier = Modifier,
106 | ) {
107 | var expandRequest: Boolean? by rememberSampleExpandRequestHandler(state)
108 |
109 | Row(
110 | modifier = modifier
111 | .fillMaxWidth()
112 | .background(Color.LightGray),
113 | horizontalArrangement = Arrangement.Absolute.SpaceEvenly,
114 | verticalAlignment = Alignment.CenterVertically,
115 | ) {
116 | Button(
117 | onClick = { expandRequest = true },
118 | ) {
119 | Text("Expand")
120 | }
121 |
122 | Button(
123 | onClick = { expandRequest = false },
124 | ) {
125 | Text("Collapse")
126 | }
127 | }
128 | }
129 |
130 | @Preview
131 | @Composable
132 | private fun Preview() {
133 | ComposeCollapsingTopBarTheme {
134 | ManualCollapsingControlsSampleContent(
135 | onBack = {},
136 | )
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/column/PartiallyCollapsibleColumnSample.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 Flaringapp
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.flaringapp.compose.topbar.ui.samples.column
18 |
19 | import androidx.compose.foundation.layout.WindowInsets
20 | import androidx.compose.foundation.layout.WindowInsetsSides
21 | import androidx.compose.foundation.layout.fillMaxSize
22 | import androidx.compose.foundation.layout.only
23 | import androidx.compose.foundation.shape.RoundedCornerShape
24 | import androidx.compose.material3.Surface
25 | import androidx.compose.runtime.Composable
26 | import androidx.compose.ui.Modifier
27 | import androidx.compose.ui.tooling.preview.Preview
28 | import androidx.compose.ui.unit.dp
29 | import com.flaringapp.compose.topbar.nestedcollapse.CollapsingTopBarColumn
30 | import com.flaringapp.compose.topbar.nestedcollapse.CollapsingTopBarColumnDirection
31 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffold
32 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldScrollMode
33 | import com.flaringapp.compose.topbar.screen
34 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSample
35 | import com.flaringapp.compose.topbar.ui.samples.common.SampleContent
36 | import com.flaringapp.compose.topbar.ui.samples.common.SampleFilterChips
37 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopAppBar
38 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopBarBanner
39 | import com.flaringapp.compose.topbar.ui.samples.common.SampleVerticalFadingEdge
40 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
41 |
42 | object PartiallyCollapsibleColumnSample : CollapsingTopBarSample {
43 |
44 | override val name: String = "Partially Collapsible Column"
45 |
46 | @Composable
47 | override fun Content(onBack: () -> Unit) {
48 | PartiallyCollapsibleColumnSampleContent(
49 | onBack = onBack,
50 | isReversed = false,
51 | )
52 | }
53 | }
54 |
55 | object ReverseCollapsibleColumnSample : CollapsingTopBarSample {
56 |
57 | override val name: String = "Reverse Collapsible Column"
58 |
59 | @Composable
60 | override fun Content(onBack: () -> Unit) {
61 | PartiallyCollapsibleColumnSampleContent(
62 | onBack = onBack,
63 | isReversed = true,
64 | )
65 | }
66 | }
67 |
68 | @Composable
69 | fun PartiallyCollapsibleColumnSampleContent(
70 | onBack: () -> Unit,
71 | isReversed: Boolean,
72 | modifier: Modifier = Modifier,
73 | ) {
74 | Surface(
75 | modifier = modifier.fillMaxSize(),
76 | ) {
77 | CollapsingContent(
78 | onBack = onBack,
79 | isReversed = isReversed,
80 | )
81 | }
82 | }
83 |
84 | @Composable
85 | private fun CollapsingContent(
86 | onBack: () -> Unit,
87 | isReversed: Boolean,
88 | modifier: Modifier = Modifier,
89 | ) {
90 | CollapsingTopBarScaffold(
91 | modifier = modifier,
92 | scrollMode = CollapsingTopBarScaffoldScrollMode.collapse(expandAlways = false),
93 | topBar = { topBarState ->
94 | val collapseDirection = if (isReversed) {
95 | CollapsingTopBarColumnDirection.TopToBottom
96 | } else {
97 | CollapsingTopBarColumnDirection.BottomUp
98 | }
99 |
100 | CollapsingTopBarColumn(
101 | state = topBarState,
102 | collapseDirection = collapseDirection,
103 | ) {
104 | SampleTopBarBanner(
105 | shape = RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp),
106 | windowInsets = WindowInsets.screen.only(WindowInsetsSides.Top),
107 | )
108 |
109 | SampleTopAppBar(
110 | modifier = Modifier.notCollapsible(),
111 | title = "Partially Collapsible Column",
112 | onBack = onBack,
113 | )
114 |
115 | SampleVerticalFadingEdge()
116 | SampleFilterChips()
117 | }
118 | },
119 | body = {
120 | SampleContent()
121 | },
122 | )
123 | }
124 |
125 | @Preview
126 | @Composable
127 | private fun Preview() {
128 | ComposeCollapsingTopBarTheme {
129 | PartiallyCollapsibleColumnSampleContent(
130 | onBack = {},
131 | isReversed = false,
132 | )
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/scaffold/ScaffoldScrollControls.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.scaffold
18 |
19 | import androidx.compose.foundation.layout.IntrinsicSize
20 | import androidx.compose.foundation.layout.fillMaxHeight
21 | import androidx.compose.foundation.layout.fillMaxWidth
22 | import androidx.compose.foundation.layout.height
23 | import androidx.compose.material3.MaterialTheme
24 | import androidx.compose.material3.SegmentedButton
25 | import androidx.compose.material3.SingleChoiceSegmentedButtonRow
26 | import androidx.compose.material3.SingleChoiceSegmentedButtonRowScope
27 | import androidx.compose.material3.Text
28 | import androidx.compose.runtime.Composable
29 | import androidx.compose.runtime.Immutable
30 | import androidx.compose.runtime.mutableStateListOf
31 | import androidx.compose.runtime.remember
32 | import androidx.compose.ui.Modifier
33 | import androidx.compose.ui.graphics.RectangleShape
34 | import androidx.compose.ui.text.style.TextAlign
35 | import androidx.compose.ui.tooling.preview.Preview
36 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldScrollMode
37 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
38 |
39 | @Composable
40 | fun ScaffoldScrollControls(
41 | selectedMode: ScaffoldScrollControlMode,
42 | selectMode: (ScaffoldScrollControlMode) -> Unit,
43 | modifier: Modifier = Modifier,
44 | ) {
45 | val modes = remember {
46 | mutableStateListOf(
47 | ScaffoldScrollControlMode.Collapse,
48 | ScaffoldScrollControlMode.CollapseAndExit,
49 | ScaffoldScrollControlMode.EnterAlwaysCollapsed,
50 | )
51 | }
52 |
53 | SingleChoiceSegmentedButtonRow(
54 | modifier = modifier.height(IntrinsicSize.Max),
55 | ) {
56 | modes.forEach { mode ->
57 | ModeButton(
58 | modifier = Modifier.fillMaxHeight(),
59 | mode = mode,
60 | selected = mode == selectedMode,
61 | select = { selectMode(mode) },
62 | )
63 | }
64 | }
65 | }
66 |
67 | @Composable
68 | private fun SingleChoiceSegmentedButtonRowScope.ModeButton(
69 | mode: ScaffoldScrollControlMode,
70 | selected: Boolean,
71 | select: () -> Unit,
72 | modifier: Modifier = Modifier,
73 | ) {
74 | SegmentedButton(
75 | modifier = modifier,
76 | selected = selected,
77 | onClick = select,
78 | shape = RectangleShape,
79 | ) {
80 | Text(
81 | text = mode.name,
82 | textAlign = TextAlign.Center,
83 | style = MaterialTheme.typography.labelMedium,
84 | )
85 | }
86 | }
87 |
88 | @Immutable
89 | sealed class ScaffoldScrollControlMode {
90 |
91 | abstract val name: String
92 |
93 | @Composable
94 | abstract fun rememberScrollMode(
95 | expandAlways: Boolean,
96 | ): CollapsingTopBarScaffoldScrollMode
97 |
98 | data object Collapse : ScaffoldScrollControlMode() {
99 |
100 | override val name: String
101 | get() = "Collapse"
102 |
103 | @Composable
104 | override fun rememberScrollMode(
105 | expandAlways: Boolean,
106 | ) = CollapsingTopBarScaffoldScrollMode.collapse(
107 | expandAlways = expandAlways,
108 | )
109 | }
110 |
111 | data object CollapseAndExit : ScaffoldScrollControlMode() {
112 |
113 | override val name: String
114 | get() = "Collapse with exit"
115 |
116 | @Composable
117 | override fun rememberScrollMode(
118 | expandAlways: Boolean,
119 | ) = CollapsingTopBarScaffoldScrollMode.collapseAndExit(
120 | expandAlways = expandAlways,
121 | )
122 | }
123 |
124 | data object EnterAlwaysCollapsed : ScaffoldScrollControlMode() {
125 |
126 | override val name: String
127 | get() = "Enter always collapsed"
128 |
129 | @Composable
130 | override fun rememberScrollMode(expandAlways: Boolean) =
131 | CollapsingTopBarScaffoldScrollMode.enterAlwaysCollapsed()
132 | }
133 | }
134 |
135 | @Preview(showBackground = true)
136 | @Composable
137 | private fun Preview() {
138 | ComposeCollapsingTopBarTheme {
139 | ScaffoldScrollControls(
140 | modifier = Modifier.fillMaxWidth(),
141 | selectedMode = ScaffoldScrollControlMode.EnterAlwaysCollapsed,
142 | selectMode = {},
143 | )
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/column/ColumnInStackSample.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.column
18 |
19 | import androidx.compose.foundation.background
20 | import androidx.compose.foundation.layout.Spacer
21 | import androidx.compose.foundation.layout.WindowInsets
22 | import androidx.compose.foundation.layout.WindowInsetsSides
23 | import androidx.compose.foundation.layout.fillMaxSize
24 | import androidx.compose.foundation.layout.only
25 | import androidx.compose.foundation.layout.windowInsetsPadding
26 | import androidx.compose.foundation.shape.RoundedCornerShape
27 | import androidx.compose.material3.MaterialTheme
28 | import androidx.compose.material3.Surface
29 | import androidx.compose.runtime.Composable
30 | import androidx.compose.ui.Modifier
31 | import androidx.compose.ui.draw.clip
32 | import androidx.compose.ui.graphics.Color
33 | import androidx.compose.ui.tooling.preview.Preview
34 | import androidx.compose.ui.unit.dp
35 | import com.flaringapp.compose.topbar.nestedcollapse.CollapsingTopBarColumn
36 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffold
37 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldScrollMode
38 | import com.flaringapp.compose.topbar.screen
39 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSample
40 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSampleDogDefaults
41 | import com.flaringapp.compose.topbar.ui.samples.common.SampleContent
42 | import com.flaringapp.compose.topbar.ui.samples.common.SampleFilterChips
43 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopAppBar
44 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopBarImage
45 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
46 |
47 | object ColumnInStackSample : CollapsingTopBarSample {
48 |
49 | override val name: String = "Column In Stack"
50 |
51 | @Composable
52 | override fun Content(onBack: () -> Unit) {
53 | ColumnInStackSampleContent(onBack = onBack)
54 | }
55 | }
56 |
57 | @Composable
58 | fun ColumnInStackSampleContent(
59 | onBack: () -> Unit,
60 | modifier: Modifier = Modifier,
61 | ) {
62 | Surface(
63 | modifier = modifier.fillMaxSize(),
64 | ) {
65 | CollapsingContent(
66 | onBack = onBack,
67 | )
68 | }
69 | }
70 |
71 | @Composable
72 | private fun CollapsingContent(
73 | onBack: () -> Unit,
74 | modifier: Modifier = Modifier,
75 | ) {
76 | CollapsingTopBarScaffold(
77 | modifier = modifier,
78 | scrollMode = CollapsingTopBarScaffoldScrollMode.collapse(expandAlways = true),
79 | topBar = { topBarState ->
80 | val topBarShape = RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp)
81 |
82 | SampleTopBarImage(
83 | modifier = Modifier
84 | .parallax(0.25f),
85 | dog = CollapsingTopBarSampleDogDefaults.Column,
86 | )
87 |
88 | CollapsingTopBarColumn(
89 | modifier = Modifier
90 | .background(
91 | color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.5f),
92 | shape = topBarShape,
93 | )
94 | .clip(topBarShape),
95 | state = topBarState,
96 | ) {
97 | Spacer(
98 | modifier = Modifier
99 | .windowInsetsPadding(WindowInsets.screen.only(WindowInsetsSides.Top))
100 | .notCollapsible(),
101 | )
102 |
103 | SampleFilterChips(
104 | modifier = Modifier.clipToCollapse(),
105 | )
106 |
107 | SampleTopAppBar(
108 | modifier = Modifier.notCollapsible(),
109 | title = "Column In Stack",
110 | onBack = onBack,
111 | ignoreWindowInsets = true,
112 | containerColor = Color.Transparent,
113 | )
114 |
115 | SampleFilterChips(
116 | modifier = Modifier.clipToCollapse(),
117 | )
118 | }
119 | },
120 | body = {
121 | SampleContent()
122 | },
123 | )
124 | }
125 |
126 | @Preview
127 | @Composable
128 | private fun Preview() {
129 | ComposeCollapsingTopBarTheme {
130 | ColumnInStackSampleContent(
131 | onBack = {},
132 | )
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/column/AlternatelyCollapsibleColumnSample.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 Flaringapp
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.flaringapp.compose.topbar.ui.samples.column
18 |
19 | import androidx.compose.foundation.layout.PaddingValues
20 | import androidx.compose.foundation.layout.WindowInsets
21 | import androidx.compose.foundation.layout.WindowInsetsSides
22 | import androidx.compose.foundation.layout.asPaddingValues
23 | import androidx.compose.foundation.layout.fillMaxSize
24 | import androidx.compose.foundation.layout.fillMaxWidth
25 | import androidx.compose.foundation.layout.only
26 | import androidx.compose.foundation.layout.padding
27 | import androidx.compose.foundation.shape.RoundedCornerShape
28 | import androidx.compose.material3.MaterialTheme
29 | import androidx.compose.material3.Surface
30 | import androidx.compose.material3.Text
31 | import androidx.compose.runtime.Composable
32 | import androidx.compose.ui.Modifier
33 | import androidx.compose.ui.text.style.TextAlign
34 | import androidx.compose.ui.tooling.preview.Preview
35 | import androidx.compose.ui.unit.dp
36 | import androidx.compose.ui.zIndex
37 | import com.flaringapp.compose.topbar.nestedcollapse.CollapsingTopBarColumn
38 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffold
39 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldScrollMode
40 | import com.flaringapp.compose.topbar.screen
41 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSample
42 | import com.flaringapp.compose.topbar.ui.samples.common.SampleContent
43 | import com.flaringapp.compose.topbar.ui.samples.common.SampleFilterChips
44 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopAppBar
45 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopBarBanner
46 | import com.flaringapp.compose.topbar.ui.samples.common.SampleVerticalFadingEdge
47 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
48 |
49 | object AlternatelyCollapsibleColumnSample : CollapsingTopBarSample {
50 |
51 | override val name: String = "Alternately Collapsible Column"
52 |
53 | @Composable
54 | override fun Content(onBack: () -> Unit) {
55 | AlternatelyCollapsibleColumnSampleContent(
56 | onBack = onBack,
57 | )
58 | }
59 | }
60 |
61 | @Composable
62 | fun AlternatelyCollapsibleColumnSampleContent(
63 | onBack: () -> Unit,
64 | modifier: Modifier = Modifier,
65 | ) {
66 | Surface(
67 | modifier = modifier.fillMaxSize(),
68 | ) {
69 | CollapsingContent(
70 | onBack = onBack,
71 | )
72 | }
73 | }
74 |
75 | @Composable
76 | private fun CollapsingContent(
77 | onBack: () -> Unit,
78 | modifier: Modifier = Modifier,
79 | ) {
80 | CollapsingTopBarScaffold(
81 | modifier = modifier,
82 | scrollMode = CollapsingTopBarScaffoldScrollMode.collapse(expandAlways = false),
83 | topBar = { topBarState ->
84 | CollapsingTopBarColumn(topBarState) {
85 | val topWindowInsetsPadding =
86 | WindowInsets.screen.only(WindowInsetsSides.Top).asPaddingValues()
87 |
88 | FixedElement(
89 | modifier = Modifier.notCollapsible(),
90 | text = "Fixed top bar header",
91 | contentPadding = topWindowInsetsPadding,
92 | )
93 |
94 | SampleTopBarBanner(
95 | shape = RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp),
96 | )
97 |
98 | SampleTopAppBar(
99 | modifier = Modifier.notCollapsible(),
100 | title = "Alternately Collapsible Column",
101 | onBack = onBack,
102 | ignoreWindowInsets = true,
103 | )
104 |
105 | SampleVerticalFadingEdge()
106 | SampleFilterChips()
107 |
108 | FixedElement(
109 | modifier = Modifier
110 | .notCollapsible()
111 | .zIndex(1f),
112 | text = "Fixed top bar footer",
113 | )
114 | }
115 | },
116 | body = {
117 | SampleContent()
118 | },
119 | )
120 | }
121 |
122 | @Composable
123 | private fun FixedElement(
124 | text: String,
125 | modifier: Modifier = Modifier,
126 | contentPadding: PaddingValues = PaddingValues(),
127 | ) {
128 | Surface(
129 | modifier = modifier.fillMaxWidth(),
130 | color = MaterialTheme.colorScheme.surfaceContainerHighest,
131 | ) {
132 | Text(
133 | modifier = Modifier.padding(contentPadding),
134 | text = text,
135 | textAlign = TextAlign.Center,
136 | )
137 | }
138 | }
139 |
140 | @Preview
141 | @Composable
142 | private fun Preview() {
143 | ComposeCollapsingTopBarTheme {
144 | AlternatelyCollapsibleColumnSampleContent(
145 | onBack = {},
146 | )
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/column/ColumnMovingElementSample.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.column
18 |
19 | import androidx.compose.foundation.background
20 | import androidx.compose.foundation.layout.Arrangement
21 | import androidx.compose.foundation.layout.Box
22 | import androidx.compose.foundation.layout.Row
23 | import androidx.compose.foundation.layout.fillMaxSize
24 | import androidx.compose.foundation.layout.fillMaxWidth
25 | import androidx.compose.foundation.layout.padding
26 | import androidx.compose.foundation.layout.size
27 | import androidx.compose.foundation.shape.CircleShape
28 | import androidx.compose.material3.Surface
29 | import androidx.compose.material3.Text
30 | import androidx.compose.runtime.Composable
31 | import androidx.compose.runtime.derivedStateOf
32 | import androidx.compose.runtime.getValue
33 | import androidx.compose.runtime.mutableFloatStateOf
34 | import androidx.compose.runtime.remember
35 | import androidx.compose.runtime.setValue
36 | import androidx.compose.ui.Alignment
37 | import androidx.compose.ui.Modifier
38 | import androidx.compose.ui.graphics.Color
39 | import androidx.compose.ui.graphics.graphicsLayer
40 | import androidx.compose.ui.tooling.preview.Preview
41 | import androidx.compose.ui.unit.dp
42 | import androidx.compose.ui.unit.lerp
43 | import androidx.compose.ui.zIndex
44 | import com.flaringapp.compose.topbar.nestedcollapse.CollapsingTopBarColumn
45 | import com.flaringapp.compose.topbar.nestedcollapse.CollapsingTopBarColumnScope
46 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffold
47 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldScrollMode
48 | import com.flaringapp.compose.topbar.ui.samples.CollapsingTopBarSample
49 | import com.flaringapp.compose.topbar.ui.samples.common.SampleContent
50 | import com.flaringapp.compose.topbar.ui.samples.common.SampleTopAppBar
51 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
52 |
53 | object ColumnMovingElementSample : CollapsingTopBarSample {
54 |
55 | override val name: String = "Column Moving Element"
56 |
57 | @Composable
58 | override fun Content(onBack: () -> Unit) {
59 | ColumnMovingElementSampleContent(onBack = onBack)
60 | }
61 | }
62 |
63 | @Composable
64 | fun ColumnMovingElementSampleContent(
65 | onBack: () -> Unit,
66 | modifier: Modifier = Modifier,
67 | ) {
68 | Surface(
69 | modifier = modifier.fillMaxSize(),
70 | ) {
71 | CollapsingContent(
72 | onBack = onBack,
73 | )
74 | }
75 | }
76 |
77 | @Composable
78 | private fun CollapsingContent(
79 | onBack: () -> Unit,
80 | modifier: Modifier = Modifier,
81 | ) {
82 | CollapsingTopBarScaffold(
83 | modifier = modifier,
84 | scrollMode = CollapsingTopBarScaffoldScrollMode.collapse(expandAlways = false),
85 | topBar = { topBarState ->
86 | CollapsingTopBarColumn(topBarState) {
87 | SampleTopAppBar(
88 | modifier = Modifier.notCollapsible(),
89 | title = "Column Moving Element",
90 | onBack = onBack,
91 | )
92 |
93 | CollapsibleSlidingAction(
94 | modifier = Modifier.zIndex(1f),
95 | )
96 | }
97 | },
98 | body = {
99 | SampleContent()
100 | },
101 | )
102 | }
103 |
104 | @Composable
105 | private fun CollapsingTopBarColumnScope.CollapsibleSlidingAction(
106 | modifier: Modifier = Modifier,
107 | ) {
108 | var collapseProgress by remember {
109 | mutableFloatStateOf(1f)
110 | }
111 | val twiceAsFastCollapseProgress by remember {
112 | derivedStateOf {
113 | ((collapseProgress - 0.5f) * 2).coerceAtLeast(0f)
114 | }
115 | }
116 |
117 | Row(
118 | modifier = modifier
119 | .fillMaxWidth()
120 | .padding(16.dp)
121 | .columnProgress { _, itemProgress -> collapseProgress = itemProgress },
122 | horizontalArrangement = Arrangement.SpaceBetween,
123 | verticalAlignment = Alignment.CenterVertically,
124 | ) {
125 | Text(
126 | modifier = Modifier.graphicsLayer {
127 | alpha = twiceAsFastCollapseProgress
128 | translationY = size.height * (1f - collapseProgress)
129 | },
130 | text = "Collapsible element",
131 | )
132 |
133 | Box(
134 | modifier = Modifier
135 | .size(40.dp)
136 | .graphicsLayer {
137 | translationX = lerp(56.dp, 0.dp, twiceAsFastCollapseProgress).toPx()
138 | }
139 | .background(color = Color.Red, shape = CircleShape),
140 | )
141 | }
142 | }
143 |
144 | @Preview
145 | @Composable
146 | private fun Preview() {
147 | ComposeCollapsingTopBarTheme {
148 | ColumnMovingElementSampleContent(
149 | onBack = {},
150 | )
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/scaffold/ScaffoldStateControls.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.scaffold
18 |
19 | import androidx.compose.foundation.layout.Column
20 | import androidx.compose.foundation.layout.Row
21 | import androidx.compose.foundation.layout.fillMaxWidth
22 | import androidx.compose.foundation.layout.padding
23 | import androidx.compose.material3.MaterialTheme
24 | import androidx.compose.material3.Text
25 | import androidx.compose.material3.TextButton
26 | import androidx.compose.runtime.Composable
27 | import androidx.compose.runtime.getValue
28 | import androidx.compose.runtime.setValue
29 | import androidx.compose.ui.Alignment
30 | import androidx.compose.ui.Modifier
31 | import androidx.compose.ui.text.AnnotatedString
32 | import androidx.compose.ui.text.SpanStyle
33 | import androidx.compose.ui.text.buildAnnotatedString
34 | import androidx.compose.ui.text.font.FontWeight
35 | import androidx.compose.ui.text.withStyle
36 | import androidx.compose.ui.tooling.preview.Preview
37 | import androidx.compose.ui.unit.dp
38 | import com.flaringapp.compose.topbar.CollapsingTopBarControls
39 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldState
40 | import com.flaringapp.compose.topbar.scaffold.rememberCollapsingTopBarScaffoldState
41 | import com.flaringapp.compose.topbar.ui.samples.common.rememberSampleExpandRequestHandler
42 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
43 |
44 | @Composable
45 | fun ScaffoldStateControls(
46 | state: CollapsingTopBarScaffoldState,
47 | modifier: Modifier = Modifier,
48 | ) {
49 | Row(
50 | modifier = modifier.fillMaxWidth(),
51 | ) {
52 | ControlsItem(
53 | modifier = Modifier.weight(1f),
54 | state = state,
55 | name = "Scaffold",
56 | isExpanded = { it.isExpanded },
57 | isCollapsed = { it.isCollapsed },
58 | )
59 |
60 | ControlsItem(
61 | modifier = Modifier.weight(1f),
62 | state = state.topBarState,
63 | name = "Top Bar",
64 | isExpanded = { it.isExpanded },
65 | isCollapsed = { it.isCollapsed },
66 | )
67 |
68 | if (state.exitState.isEnabled) {
69 | ControlsItem(
70 | modifier = Modifier.weight(1f),
71 | state = state.exitState,
72 | name = "Exit",
73 | isExpandedName = "Entered",
74 | isCollapsedName = "Exited",
75 | expandName = "Enter",
76 | collapseName = "Exit",
77 | isExpanded = { it.isFullyEntered },
78 | isCollapsed = { it.isFullyExited },
79 | )
80 | }
81 | }
82 | }
83 |
84 | @Composable
85 | private fun ControlsItem(
86 | state: State,
87 | name: String,
88 | isExpanded: (State) -> Boolean,
89 | isCollapsed: (State) -> Boolean,
90 | modifier: Modifier = Modifier,
91 | isExpandedName: String = "Expanded",
92 | isCollapsedName: String = "Collapsed",
93 | expandName: String = "Expand",
94 | collapseName: String = "Collapse",
95 | ) {
96 | var expandRequest: Boolean? by rememberSampleExpandRequestHandler(state)
97 |
98 | Column(
99 | modifier = modifier,
100 | horizontalAlignment = Alignment.CenterHorizontally,
101 | ) {
102 | Text(
103 | text = name,
104 | style = MaterialTheme.typography.titleMedium,
105 | )
106 |
107 | Text(
108 | modifier = Modifier.padding(top = 8.dp),
109 | text = formatStateText(
110 | stateName = isExpandedName,
111 | state = isExpanded(state),
112 | ),
113 | style = MaterialTheme.typography.bodyMedium,
114 | )
115 |
116 | Text(
117 | modifier = Modifier.padding(top = 8.dp),
118 | text = formatStateText(
119 | stateName = isCollapsedName,
120 | state = isCollapsed(state),
121 | ),
122 | style = MaterialTheme.typography.bodyMedium,
123 | )
124 |
125 | TextButton(
126 | onClick = { expandRequest = true },
127 | ) {
128 | Text(expandName)
129 | }
130 |
131 | TextButton(
132 | onClick = { expandRequest = false },
133 | ) {
134 | Text(collapseName)
135 | }
136 | }
137 | }
138 |
139 | private fun formatStateText(
140 | stateName: String,
141 | state: Boolean,
142 | ): AnnotatedString = buildAnnotatedString {
143 | append(stateName)
144 | append(": ")
145 | withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
146 | append(state.toString())
147 | }
148 | }
149 |
150 | @Preview(showBackground = true)
151 | @Composable
152 | private fun Preview() {
153 | ComposeCollapsingTopBarTheme {
154 | ScaffoldStateControls(
155 | state = rememberCollapsingTopBarScaffoldState(),
156 | )
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flaringapp/compose/topbar/ui/samples/common/SampleTopBarBanner.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.ui.samples.common
18 |
19 | import androidx.compose.animation.core.LinearEasing
20 | import androidx.compose.animation.core.RepeatMode
21 | import androidx.compose.animation.core.animateFloat
22 | import androidx.compose.animation.core.infiniteRepeatable
23 | import androidx.compose.animation.core.rememberInfiniteTransition
24 | import androidx.compose.animation.core.tween
25 | import androidx.compose.foundation.layout.Column
26 | import androidx.compose.foundation.layout.WindowInsets
27 | import androidx.compose.foundation.layout.fillMaxWidth
28 | import androidx.compose.foundation.layout.padding
29 | import androidx.compose.foundation.layout.windowInsetsPadding
30 | import androidx.compose.material3.ColorScheme
31 | import androidx.compose.material3.LocalTextStyle
32 | import androidx.compose.material3.MaterialTheme
33 | import androidx.compose.material3.Surface
34 | import androidx.compose.material3.Text
35 | import androidx.compose.runtime.Composable
36 | import androidx.compose.runtime.getValue
37 | import androidx.compose.runtime.remember
38 | import androidx.compose.ui.Modifier
39 | import androidx.compose.ui.geometry.Offset
40 | import androidx.compose.ui.geometry.Size
41 | import androidx.compose.ui.graphics.Brush
42 | import androidx.compose.ui.graphics.LinearGradientShader
43 | import androidx.compose.ui.graphics.RectangleShape
44 | import androidx.compose.ui.graphics.Shader
45 | import androidx.compose.ui.graphics.ShaderBrush
46 | import androidx.compose.ui.graphics.Shape
47 | import androidx.compose.ui.graphics.TileMode
48 | import androidx.compose.ui.text.AnnotatedString
49 | import androidx.compose.ui.text.SpanStyle
50 | import androidx.compose.ui.text.buildAnnotatedString
51 | import androidx.compose.ui.text.font.FontWeight
52 | import androidx.compose.ui.text.withStyle
53 | import androidx.compose.ui.tooling.preview.Preview
54 | import androidx.compose.ui.unit.dp
55 | import com.flaringapp.compose.topbar.ui.theme.ComposeCollapsingTopBarTheme
56 |
57 | @Composable
58 | fun SampleTopBarBanner(
59 | modifier: Modifier = Modifier,
60 | shape: Shape = RectangleShape,
61 | windowInsets: WindowInsets = WindowInsets(0),
62 | ) {
63 | Surface(
64 | modifier = modifier.fillMaxWidth(),
65 | color = MaterialTheme.colorScheme.secondaryContainer,
66 | shape = shape,
67 | ) {
68 | Column(
69 | modifier = Modifier
70 | .windowInsetsPadding(windowInsets)
71 | .padding(horizontal = 24.dp, vertical = 8.dp),
72 | ) {
73 | Text(
74 | text = "Save your time",
75 | style = LocalTextStyle.current.copy(
76 | brush = rememberTitleAnimatedBrush(),
77 | ),
78 | fontWeight = FontWeight.Bold,
79 | )
80 |
81 | Text(
82 | modifier = Modifier.padding(top = 4.dp),
83 | text = formatBannerMessage(colorScheme = MaterialTheme.colorScheme),
84 | )
85 | }
86 | }
87 | }
88 |
89 | private fun formatBannerMessage(
90 | colorScheme: ColorScheme,
91 | ): AnnotatedString = buildAnnotatedString {
92 | append("With ")
93 | withStyle(SpanStyle(color = colorScheme.primary, fontWeight = FontWeight.Bold)) {
94 | append("ComposeCollapsingTopBar")
95 | }
96 | }
97 |
98 | @Composable
99 | private fun rememberTitleAnimatedBrush(): Brush {
100 | val colorScheme = MaterialTheme.colorScheme
101 |
102 | val infiniteTransition = rememberInfiniteTransition(
103 | label = "BrushOffsetTransition",
104 | )
105 |
106 | val offset by infiniteTransition.animateFloat(
107 | label = "BrushOffsetAnimation",
108 | initialValue = 0f,
109 | targetValue = 1f,
110 | animationSpec = infiniteRepeatable(
111 | animation = tween(durationMillis = 2000, easing = LinearEasing),
112 | repeatMode = RepeatMode.Restart,
113 | ),
114 | )
115 |
116 | return remember(colorScheme, offset) {
117 | val colors = listOf(
118 | colorScheme.error,
119 | colorScheme.tertiary,
120 | colorScheme.inversePrimary,
121 | colorScheme.error,
122 | colorScheme.tertiary,
123 | )
124 |
125 | object : ShaderBrush() {
126 | override fun createShader(size: Size): Shader {
127 | val widthOffset = size.width * offset
128 | val heightOffset = size.height * offset
129 | return LinearGradientShader(
130 | colors = colors,
131 | from = Offset(widthOffset, heightOffset),
132 | to = Offset(widthOffset + size.width, heightOffset + size.height),
133 | tileMode = TileMode.Mirror,
134 | )
135 | }
136 | }
137 | }
138 | }
139 |
140 | @Preview
141 | @Composable
142 | private fun Preview() {
143 | ComposeCollapsingTopBarTheme {
144 | SampleTopBarBanner()
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/ComposeCollapsingTopBar/src/main/java/com/flaringapp/compose/topbar/nestedscroll/CollapsingTopBarNestedScrollExpand.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.nestedscroll
18 |
19 | import androidx.compose.foundation.gestures.FlingBehavior
20 | import androidx.compose.foundation.gestures.ScrollableState
21 | import androidx.compose.ui.geometry.Offset
22 | import androidx.compose.ui.input.nestedscroll.NestedScrollSource
23 | import androidx.compose.ui.unit.Velocity
24 | import com.flaringapp.compose.topbar.nestedscroll.CollapsingTopBarNestedScrollExpand.Always
25 | import com.flaringapp.compose.topbar.nestedscroll.CollapsingTopBarNestedScrollExpand.AtTop
26 |
27 | /**
28 | * An origin class that encapsulates expand scroll handling logic.
29 | *
30 | * Expanding in terms of this handler means dispatching scroll to [state], which is in its turn
31 | * responsible for further processing.
32 | *
33 | * @see Always
34 | * @see AtTop
35 | */
36 | abstract class CollapsingTopBarNestedScrollExpand : CollapsingTopBarNestedScrollHandler {
37 |
38 | companion object {
39 |
40 | /**
41 | * Creates one of [Always], [AtTop] scroll handlers based on [enterAlways] flag.
42 | *
43 | * @param state the scrollable top bar state that expands.
44 | * @param flingBehavior the fling behavior to be used for animating [state] fling.
45 | * @param enterAlways the flag to create [Always] handler if true, or [AtTop] if false.
46 | */
47 | fun of(
48 | state: ScrollableState,
49 | flingBehavior: FlingBehavior,
50 | enterAlways: Boolean,
51 | ): CollapsingTopBarNestedScrollHandler {
52 | return if (enterAlways) {
53 | Always(
54 | state = state,
55 | flingBehavior = flingBehavior,
56 | )
57 | } else {
58 | AtTop(
59 | state = state,
60 | flingBehavior = flingBehavior,
61 | )
62 | }
63 | }
64 | }
65 |
66 | abstract val state: ScrollableState
67 | abstract val flingBehavior: FlingBehavior
68 |
69 | /**
70 | * Dispatch [available] offset to [state] if it's downward.
71 | *
72 | * @param available the offset to be dispatched.
73 | *
74 | * @return the amount of offset consumed.
75 | */
76 | protected fun expand(available: Offset): Offset {
77 | val dy = available.y
78 | if (dy <= 0) {
79 | return Offset.Zero
80 | }
81 |
82 | val consume = state.dispatchRawDelta(dy)
83 | return Offset(0f, consume)
84 | }
85 |
86 | /**
87 | * Dispatch [available] velocity to [state] with [flingBehavior] if it's downward.
88 | *
89 | * @param available the velocity to be dispatched.
90 | *
91 | * @return the amount of velocity consumed.
92 | */
93 | protected suspend fun expand(available: Velocity): Velocity {
94 | val dy = available.y
95 | if (dy <= 0) {
96 | return Velocity.Zero
97 | }
98 |
99 | val consume = dy - state.fling(flingBehavior, dy)
100 | return Velocity(x = 0f, y = consume)
101 | }
102 |
103 | /**
104 | * A top bar nested scroll handler that expands [state] while receiving pre- scroll and fling.
105 | * Consumes available scroll before children, so that top bar expands anywhere.
106 | *
107 | * @param state the scrollable top bar state that expands.
108 | * @param flingBehavior the fling behavior to be used for animating [state] fling.
109 | */
110 | class Always(
111 | override val state: ScrollableState,
112 | override val flingBehavior: FlingBehavior,
113 | ) : CollapsingTopBarNestedScrollExpand() {
114 |
115 | override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
116 | return expand(available)
117 | }
118 |
119 | override suspend fun onPreFling(available: Velocity): Velocity {
120 | return expand(available)
121 | }
122 | }
123 |
124 | /**
125 | * A top bar nested scroll handler that expands [state] while receiving post- scroll and fling.
126 | * Consumes available scroll after children, so that top bar expands only when scrollable
127 | * children no longer consume downward scroll (are at top).
128 | *
129 | * @param state the scrollable top bar state that expands.
130 | * @param flingBehavior the fling behavior to be used for animating [state] fling.
131 | */
132 | class AtTop(
133 | override val state: ScrollableState,
134 | override val flingBehavior: FlingBehavior,
135 | ) : CollapsingTopBarNestedScrollExpand() {
136 |
137 | override fun onPostScroll(
138 | consumed: Offset,
139 | available: Offset,
140 | source: NestedScrollSource,
141 | ): Offset {
142 | return expand(available)
143 | }
144 |
145 | override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
146 | return expand(available)
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/ComposeCollapsingTopBar/src/main/java/com/flaringapp/compose/topbar/scaffold/CollapsingTopBarScaffoldScrollMode.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Flaringapp
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.flaringapp.compose.topbar.scaffold
18 |
19 | import androidx.compose.foundation.gestures.FlingBehavior
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.runtime.annotation.RememberInComposition
22 | import androidx.compose.runtime.remember
23 | import com.flaringapp.compose.topbar.nestedscroll.CollapsingTopBarNestedScrollCollapse
24 | import com.flaringapp.compose.topbar.nestedscroll.CollapsingTopBarNestedScrollExpand
25 | import com.flaringapp.compose.topbar.nestedscroll.CollapsingTopBarNestedScrollHandler
26 | import com.flaringapp.compose.topbar.nestedscroll.CollapsingTopBarNestedScrollSnap
27 | import com.flaringapp.compose.topbar.nestedscroll.CollapsingTopBarNestedScrollStrategy
28 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldScrollMode.Companion.collapse
29 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldScrollMode.Companion.collapseAndExit
30 | import com.flaringapp.compose.topbar.scaffold.CollapsingTopBarScaffoldScrollMode.Companion.enterAlwaysCollapsed
31 | import com.flaringapp.compose.topbar.snap.CollapsingTopBarSnapBehavior
32 | import com.flaringapp.compose.topbar.snap.CollapsingTopBarSnapScope
33 |
34 | /**
35 | * A set of common collapsing modes that define how top bar behaves on content scroll.
36 | *
37 | * @see collapse
38 | * @see collapseAndExit
39 | * @see enterAlwaysCollapsed
40 | */
41 | @ConsistentCopyVisibility
42 | data class CollapsingTopBarScaffoldScrollMode @RememberInComposition internal constructor(
43 | val expandAlways: Boolean,
44 | val exit: Exit? = null,
45 | ) : CollapsingTopBarNestedScrollStrategy {
46 |
47 | @ConsistentCopyVisibility
48 | data class Exit @RememberInComposition internal constructor(
49 | val enterAlways: Boolean,
50 | )
51 |
52 | val canExit: Boolean
53 | get() = exit != null
54 |
55 | override fun createHandlers(
56 | state: CollapsingTopBarScaffoldState,
57 | flingBehavior: FlingBehavior,
58 | snapBehavior: CollapsingTopBarSnapBehavior,
59 | ): List = buildList {
60 | this += CollapsingTopBarNestedScrollCollapse(
61 | state = state.topBarState,
62 | flingBehavior = flingBehavior,
63 | )
64 | if (exit != null) {
65 | this += CollapsingTopBarNestedScrollCollapse(
66 | state = state.exitState,
67 | flingBehavior = flingBehavior,
68 | )
69 | this += CollapsingTopBarNestedScrollExpand.of(
70 | state = state.exitState,
71 | flingBehavior = flingBehavior,
72 | enterAlways = exit.enterAlways,
73 | )
74 | }
75 | this += CollapsingTopBarNestedScrollExpand.of(
76 | state = state.topBarState,
77 | flingBehavior = flingBehavior,
78 | enterAlways = expandAlways,
79 | )
80 | this += CollapsingTopBarNestedScrollSnap(
81 | snapBehavior = snapBehavior,
82 | snapScope = createSnapScope(state),
83 | )
84 | }
85 |
86 | private fun createSnapScope(
87 | scaffoldState: CollapsingTopBarScaffoldState,
88 | ) = CollapsingTopBarSnapScope { wasMovingUp, action ->
89 | val delegateScope = when {
90 | exit == null -> scaffoldState.topBarState
91 | exit.enterAlways == expandAlways -> scaffoldState
92 | else -> resolveEnterAlwaysCollapsedSnapScope(scaffoldState, wasMovingUp)
93 | }
94 |
95 | delegateScope.snapWithProgress(wasMovingUp, action)
96 | }
97 |
98 | private fun resolveEnterAlwaysCollapsedSnapScope(
99 | scaffoldState: CollapsingTopBarScaffoldState,
100 | wasMovingUp: Boolean,
101 | ): CollapsingTopBarSnapScope {
102 | return when {
103 | wasMovingUp -> scaffoldState
104 | scaffoldState.topBarState.isCollapsed -> scaffoldState.exitState
105 | else -> scaffoldState.topBarState
106 | }
107 | }
108 |
109 | companion object {
110 |
111 | /**
112 | * Create and remember scroll mode in which top bar just collapses, and never exits.
113 | *
114 | * @param expandAlways the flag to determine whether top bar can expand as soon as content
115 | * scrolls upwards, or only when scrollable content underneath is fully scrolled to the top.
116 | */
117 | @Composable
118 | fun collapse(
119 | expandAlways: Boolean,
120 | ): CollapsingTopBarScaffoldScrollMode = remember(expandAlways) {
121 | CollapsingTopBarScaffoldScrollMode(
122 | expandAlways = expandAlways,
123 | )
124 | }
125 |
126 | /**
127 | * Create and remember scroll mode in which top bar sequentially collapses and exits.
128 | *
129 | * While scrolling down, the motion is: `expanded -> collapsed -> exited`. While scrolling
130 | * up, the motion is reversed: `exited -> collapsed -> expanded`.
131 | *
132 | * @param expandAlways the flag to determine whether top bar can expand as soon as content
133 | * scrolls upwards, or only when scrollable content underneath is fully scrolled to the top.
134 | */
135 | @Composable
136 | fun collapseAndExit(
137 | expandAlways: Boolean,
138 | ): CollapsingTopBarScaffoldScrollMode = remember(expandAlways) {
139 | CollapsingTopBarScaffoldScrollMode(
140 | expandAlways = expandAlways,
141 | exit = Exit(
142 | enterAlways = expandAlways,
143 | ),
144 | )
145 | }
146 |
147 | /**
148 | * Create and remember scroll mode in which top bar sequentially collapses and exits,
149 | * but may enter collapsed while content underneath is scrolling up, and expand at top.
150 | *
151 | * While scrolling down, the motion is: `expanded -> collapsed -> exited`. While scrolling
152 | * up, the motion is reversed: `exited -> collapsed -> (content scrolls to the top) ->
153 | * expanded`.
154 | */
155 | @Composable
156 | fun enterAlwaysCollapsed(): CollapsingTopBarScaffoldScrollMode = remember {
157 | CollapsingTopBarScaffoldScrollMode(
158 | expandAlways = false,
159 | exit = Exit(
160 | enterAlways = true,
161 | ),
162 | )
163 | }
164 | }
165 | }
166 |
--------------------------------------------------------------------------------