Skip to content

Instantly share code, notes, and snippets.

@axiel7
Last active May 27, 2023 03:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save axiel7/54417f67858a0d98c5860940493e0020 to your computer and use it in GitHub Desktop.
Save axiel7/54417f67858a0d98c5860940493e0020 to your computer and use it in GitHub Desktop.
Custom implementation of Material3 TopAppBar with custom content
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.FastOutLinearInEasing
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import kotlin.math.roundToInt
private val TopAppBarHeight = 64.dp
/**
* Custom implementation of Material3 TopAppBar with custom content
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopAppBarWithContent(
modifier: Modifier = Modifier,
windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
containerColor: Color = MaterialTheme.colorScheme.surface,
scrolledContainerColor: Color = MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp),
scrollBehavior: TopAppBarScrollBehavior,
applyHeightConstraint: Boolean = true,
content: @Composable () -> Unit,
) {
// Sets the app bar's height offset to collapse the entire bar's height when content is
// scrolled.
val heightOffsetLimit = with(LocalDensity.current) { -TopAppBarHeight.toPx() }
SideEffect {
if (scrollBehavior.state.heightOffsetLimit != heightOffsetLimit) {
scrollBehavior.state.heightOffsetLimit = heightOffsetLimit
}
}
// Obtain the container color from the TopAppBarColors using the `overlapFraction`. This
// ensures that the colors will adjust whether the app bar behavior is pinned or scrolled.
// This may potentially animate or interpolate a transition between the container-color and the
// container's scrolled-color according to the app bar's scroll state.
val colorTransitionFraction = scrollBehavior.state.overlappedFraction
val fraction = if (colorTransitionFraction > 0.01f) 1f else 0f
val appBarContainerColor by animateColorAsState(
targetValue = lerp(
containerColor,
scrolledContainerColor,
FastOutLinearInEasing.transform(fraction)
),
animationSpec = spring(stiffness = Spring.StiffnessMediumLow)
)
// The surface's background color is animated as specified above.
// The height of the app bar is determined by subtracting the bar's height offset from the
// app bar's defined constant height value.
Surface(modifier = modifier, color = appBarContainerColor) {
val height = LocalDensity.current.run {
TopAppBarHeight.toPx() + scrollBehavior.state.heightOffset
}
Layout(
content = {
Box(
modifier = Modifier.layoutId("content"),
) {
content()
}
},
modifier = if (applyHeightConstraint)
Modifier
.windowInsetsPadding(windowInsets)
// clip after padding so we don't show the title over the inset area
.clipToBounds()
else Modifier,
measurePolicy = { measurables, constraints ->
val contentPlaceable =
measurables.first { it.layoutId == "content" }
.measure(constraints.copy(minWidth = 0, maxWidth = constraints.maxWidth))
val layoutHeight = if (applyHeightConstraint) height.roundToInt() else constraints.maxHeight
layout(constraints.maxWidth, layoutHeight) {
contentPlaceable.place(0, 0)
}
}
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment