Loading packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt +180 −157 Original line number Diff line number Diff line Loading @@ -41,23 +41,25 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.NonRestartableComposable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.lerp import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.AlignmentLine import androidx.compose.ui.layout.LastBaseline import androidx.compose.ui.layout.Layout Loading @@ -66,6 +68,7 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.isTraversalGroup import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextOverflow Loading @@ -80,11 +83,10 @@ import kotlin.math.abs import kotlin.math.max import kotlin.math.roundToInt private val windowInsets: WindowInsets private val safeDrawingWindowInsets: WindowInsets @Composable @NonRestartableComposable get() = WindowInsets.safeDrawing .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top) get() = WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top) @Composable internal fun CustomizedTopAppBar( Loading @@ -97,14 +99,12 @@ internal fun CustomizedTopAppBar( titleTextStyle = MaterialTheme.typography.titleMedium, navigationIcon = navigationIcon, actions = actions, windowInsets = windowInsets, windowInsets = safeDrawingWindowInsets, colors = topAppBarColors(), ) } /** * The customized LargeTopAppBar for Settings. */ /** The customized LargeTopAppBar for Settings. */ @OptIn(ExperimentalMaterial3Api::class) @Composable internal fun CustomizedLargeTopAppBar( Loading @@ -124,7 +124,7 @@ internal fun CustomizedLargeTopAppBar( navigationIcon = navigationIcon, actions = actions, colors = topAppBarColors(), windowInsets = windowInsets, windowInsets = safeDrawingWindowInsets, pinnedHeight = ContainerHeight, scrollBehavior = scrollBehavior, ) Loading @@ -134,7 +134,8 @@ internal fun CustomizedLargeTopAppBar( private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) { Text( text = title, modifier = Modifier.padding( modifier = Modifier.padding( start = SettingsDimension.itemPaddingAround, end = SettingsDimension.itemPaddingEnd, ) Loading @@ -145,7 +146,8 @@ private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) { } @Composable private fun topAppBarColors() = TopAppBarColors( private fun topAppBarColors() = TopAppBarColors( containerColor = MaterialTheme.colorScheme.settingsBackground, scrolledContainerColor = MaterialTheme.colorScheme.surfaceVariant, navigationIconContentColor = MaterialTheme.colorScheme.onSurface, Loading @@ -155,17 +157,18 @@ private fun topAppBarColors() = TopAppBarColors( /** * Represents the colors used by a top app bar in different states. * * This implementation animates the container color according to the top app bar scroll state. It * does not animate the leading, headline, or trailing colors. * * @constructor create an instance with arbitrary colors, see [TopAppBarColors] for a * factory method using the default material3 spec * @param containerColor the color used for the background of this BottomAppBar. Use * [Color.Transparent] to have no color. * @param scrolledContainerColor the container color when content is scrolled behind it * @param navigationIconContentColor the content color used for the navigation icon * @param titleContentColor the content color used for the title * @param actionIconContentColor the content color used for actions * @constructor create an instance with arbitrary colors, see [TopAppBarColors] for a factory method * using the default material3 spec */ @Stable private class TopAppBarColors( Loading @@ -180,8 +183,8 @@ private class TopAppBarColors( * Represents the container color used for the top app bar. * * A [colorTransitionFraction] provides a percentage value that can be used to generate a color. * Usually, an app bar implementation will pass in a [colorTransitionFraction] read from * the [TopAppBarState.collapsedFraction] or the [TopAppBarState.overlappedFraction]. * Usually, an app bar implementation will pass in a [colorTransitionFraction] read from the * [TopAppBarState.collapsedFraction] or the [TopAppBarState.overlappedFraction]. * * @param colorTransitionFraction a `0.0` to `1.0` value that represents a color transition * percentage Loading Loading @@ -233,7 +236,8 @@ private fun SingleRowTopAppBar( colors: TopAppBarColors, ) { // Wrap the given actions in a Row. val actionsRow = @Composable { val actionsRow = @Composable { Row( horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, Loading @@ -242,11 +246,16 @@ private fun SingleRowTopAppBar( } // Compose a Surface with a TopAppBarLayout content. Surface(color = colors.scrolledContainerColor) { Box( modifier = Modifier.drawBehind { drawRect(color = colors.scrolledContainerColor) } .semantics { isTraversalGroup = true } .pointerInput(Unit) {} ) { val height = LocalDensity.current.run { ContainerHeight.toPx() } TopAppBarLayout( modifier = Modifier .windowInsetsPadding(windowInsets) modifier = Modifier.windowInsetsPadding(windowInsets) // clip after padding so we don't show the title over the inset area .clipToBounds(), heightPx = height, Loading @@ -255,7 +264,7 @@ private fun SingleRowTopAppBar( actionIconContentColor = colors.actionIconContentColor, title = title, titleTextStyle = titleTextStyle, titleAlpha = 1f, titleAlpha = { 1f }, titleVerticalArrangement = Arrangement.Center, titleBottomPadding = 0, hideTitleSemantics = false, Loading Loading @@ -308,42 +317,40 @@ private fun TwoRowsTopAppBar( // Sets the app bar's height offset limit to hide just the bottom title area and keep top title // visible when collapsed. SideEffect { if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx.floatValue) { scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx.floatValue } } // Obtain the container Color from the TopAppBarColors using the `collapsedFraction`, as the // bottom part of this TwoRowsTopAppBar changes color at the same rate the app bar expands or // collapse. // This will 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?.collapsedFraction ?: 0f val appBarContainerColor = colors.containerColor(colorTransitionFraction) val colorTransitionFraction = { scrollBehavior?.state?.collapsedFraction ?: 0f } val appBarContainerColor = { colors.containerColor(colorTransitionFraction()) } // Wrap the given actions in a Row. val actionsRow = @Composable { val actionsRow = @Composable { Row( horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, content = actions ) } val topTitleAlpha = TopTitleAlphaEasing.transform(colorTransitionFraction) val bottomTitleAlpha = 1f - colorTransitionFraction val topTitleAlpha = { TopTitleAlphaEasing.transform(colorTransitionFraction()) } val bottomTitleAlpha = { 1f - colorTransitionFraction() } // Hide the top row title semantics when its alpha value goes below 0.5 threshold. // Hide the bottom row title semantics when the top title semantics are active. val hideTopRowSemantics = colorTransitionFraction < 0.5f val hideTopRowSemantics by remember(colorTransitionFraction) { derivedStateOf { colorTransitionFraction() < 0.5f } } val hideBottomRowSemantics = !hideTopRowSemantics // Set up support for resizing the top app bar when vertically dragging the bar itself. val appBarDragModifier = if (scrollBehavior != null && !scrollBehavior.isPinned) { val appBarDragModifier = if (scrollBehavior != null && !scrollBehavior.isPinned) { Modifier.draggable( orientation = Orientation.Vertical, state = rememberDraggableState { delta -> scrollBehavior.state.heightOffset += delta }, state = rememberDraggableState { delta -> scrollBehavior.state.heightOffset += delta }, onDragStopped = { velocity -> settleAppBar( scrollBehavior.state, Loading @@ -357,11 +364,18 @@ private fun TwoRowsTopAppBar( Modifier } Surface(modifier = modifier.then(appBarDragModifier), color = appBarContainerColor) { Box( modifier = modifier .then(appBarDragModifier) .drawBehind { drawRect(color = appBarContainerColor()) } .semantics { isTraversalGroup = true } .pointerInput(Unit) {} ) { Column { TopAppBarLayout( modifier = Modifier .windowInsetsPadding(windowInsets) modifier = Modifier.windowInsetsPadding(windowInsets) // clip after padding so we don't show the title over the inset area .clipToBounds(), heightPx = pinnedHeightPx, Loading @@ -378,27 +392,37 @@ private fun TwoRowsTopAppBar( actions = actionsRow, ) TopAppBarLayout( modifier = Modifier // only apply the horizontal sides of the window insets padding, since the top modifier = Modifier // only apply the horizontal sides of the window insets padding, since the // top // padding will always be applied by the layout above .windowInsetsPadding(windowInsets.only(WindowInsetsSides.Horizontal)) .clipToBounds(), heightPx = maxHeightPx.floatValue - pinnedHeightPx + heightPx = maxHeightPx.floatValue - pinnedHeightPx + (scrollBehavior?.state?.heightOffset ?: 0f), navigationIconContentColor = colors.navigationIconContentColor, titleContentColor = colors.titleContentColor, actionIconContentColor = colors.actionIconContentColor, title = { Box(modifier = Modifier.onGloballyPositioned { coordinates -> val measuredMaxHeightPx = density.run { MaxHeightWithoutTitle.toPx() + coordinates.size.height.toFloat() Box( modifier = Modifier.onGloballyPositioned { coordinates -> val measuredMaxHeightPx = density.run { MaxHeightWithoutTitle.toPx() + coordinates.size.height.toFloat() } // Allow larger max height for multi-line title, but do not reduce // max height to prevent flaky. if (measuredMaxHeightPx > defaultMaxHeightPx) { maxHeightPx.floatValue = measuredMaxHeightPx } }) { title() } } ) { title() } }, titleTextStyle = titleTextStyle, titleAlpha = bottomTitleAlpha, Loading Loading @@ -431,10 +455,10 @@ private fun TwoRowsTopAppBar( * @param titleAlpha the title's alpha * @param titleVerticalArrangement the title's vertical arrangement * @param titleBottomPadding the title's bottom padding * @param hideTitleSemantics hides the title node from the semantic tree. Apply this * boolean when this layout is part of a [TwoRowsTopAppBar] to hide the title's semantics * from accessibility services. This is needed to avoid having multiple titles visible to * accessibility services at the same time, when animating between collapsed / expanded states. * @param hideTitleSemantics hides the title node from the semantic tree. Apply this boolean when * this layout is part of a [TwoRowsTopAppBar] to hide the title's semantics from accessibility * services. This is needed to avoid having multiple titles visible to accessibility services at * the same time, when animating between collapsed / expanded states. * @param navigationIcon a navigation icon [Composable] * @param actions actions [Composable] * @param titleScaleDisabled whether the title font scaling is disabled. Default is disabled. Loading @@ -448,7 +472,7 @@ private fun TopAppBarLayout( actionIconContentColor: Color, title: @Composable () -> Unit, titleTextStyle: TextStyle, titleAlpha: Float, titleAlpha: () -> Float, titleVerticalArrangement: Arrangement.Vertical, titleBottomPadding: Int, hideTitleSemantics: Boolean, Loading @@ -458,27 +482,23 @@ private fun TopAppBarLayout( ) { Layout( { Box( Modifier .layoutId("navigationIcon") .padding(start = TopAppBarHorizontalPadding) ) { Box(Modifier.layoutId("navigationIcon").padding(start = TopAppBarHorizontalPadding)) { CompositionLocalProvider( LocalContentColor provides navigationIconContentColor, content = navigationIcon ) } Box( Modifier .layoutId("title") Modifier.layoutId("title") .padding(horizontal = TopAppBarHorizontalPadding) .then(if (hideTitleSemantics) Modifier.clearAndSetSemantics {} else Modifier) .graphicsLayer(alpha = titleAlpha) .graphicsLayer { alpha = titleAlpha() } ) { ProvideTextStyle(value = titleTextStyle) { CompositionLocalProvider( LocalContentColor provides titleContentColor, LocalDensity provides with(LocalDensity.current) { LocalDensity provides with(LocalDensity.current) { Density( density = density, fontScale = if (titleScaleDisabled) 1f else fontScale, Loading @@ -488,11 +508,7 @@ private fun TopAppBarLayout( ) } } Box( Modifier .layoutId("actionIcons") .padding(end = TopAppBarHorizontalPadding) ) { Box(Modifier.layoutId("actionIcons").padding(end = TopAppBarHorizontalPadding)) { CompositionLocalProvider( LocalContentColor provides actionIconContentColor, content = actions Loading @@ -502,20 +518,24 @@ private fun TopAppBarLayout( modifier = modifier ) { measurables, constraints -> val navigationIconPlaceable = measurables.first { it.layoutId == "navigationIcon" } measurables .first { it.layoutId == "navigationIcon" } .measure(constraints.copy(minWidth = 0)) val actionIconsPlaceable = measurables.first { it.layoutId == "actionIcons" } measurables .first { it.layoutId == "actionIcons" } .measure(constraints.copy(minWidth = 0)) val maxTitleWidth = if (constraints.maxWidth == Constraints.Infinity) { val maxTitleWidth = if (constraints.maxWidth == Constraints.Infinity) { constraints.maxWidth } else { (constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width) .coerceAtLeast(0) } val titlePlaceable = measurables.first { it.layoutId == "title" } measurables .first { it.layoutId == "title" } .measure(constraints.copy(minWidth = 0, maxWidth = maxTitleWidth)) // Locate the title's baseline. Loading @@ -538,13 +558,17 @@ private fun TopAppBarLayout( // Title titlePlaceable.placeRelative( x = max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width), y = when (titleVerticalArrangement) { y = when (titleVerticalArrangement) { Arrangement.Center -> (layoutHeight - titlePlaceable.height) / 2 // Apply bottom padding from the title's baseline only when the Arrangement is // "Bottom". // Apply bottom padding from the title's baseline only when the Arrangement // is "Bottom". Arrangement.Bottom -> if (titleBottomPadding == 0) layoutHeight - titlePlaceable.height else layoutHeight - titlePlaceable.height - max( else layoutHeight - titlePlaceable.height - max( 0, titleBottomPadding - titlePlaceable.height + titleBaseline ) Loading @@ -562,7 +586,6 @@ private fun TopAppBarLayout( } } /** * Settles the app bar by flinging, in case the given velocity is greater than zero, and snapping * after the fling settles. Loading Loading @@ -603,9 +626,7 @@ private suspend fun settleAppBar( } // Snap if animation specs were provided. if (snapAnimationSpec != null) { if (state.heightOffset < 0 && state.heightOffset > state.heightOffsetLimit ) { if (state.heightOffset < 0 && state.heightOffset > state.heightOffsetLimit) { AnimationState(initialValue = state.heightOffset).animateTo( if (state.collapsedFraction < 0.5f) { 0f Loading @@ -613,7 +634,9 @@ private suspend fun settleAppBar( state.heightOffsetLimit }, animationSpec = snapAnimationSpec ) { state.heightOffset = value } ) { state.heightOffset = value } } } Loading packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt +19 −240 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt +180 −157 Original line number Diff line number Diff line Loading @@ -41,23 +41,25 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.NonRestartableComposable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.lerp import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.AlignmentLine import androidx.compose.ui.layout.LastBaseline import androidx.compose.ui.layout.Layout Loading @@ -66,6 +68,7 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.isTraversalGroup import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextOverflow Loading @@ -80,11 +83,10 @@ import kotlin.math.abs import kotlin.math.max import kotlin.math.roundToInt private val windowInsets: WindowInsets private val safeDrawingWindowInsets: WindowInsets @Composable @NonRestartableComposable get() = WindowInsets.safeDrawing .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top) get() = WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top) @Composable internal fun CustomizedTopAppBar( Loading @@ -97,14 +99,12 @@ internal fun CustomizedTopAppBar( titleTextStyle = MaterialTheme.typography.titleMedium, navigationIcon = navigationIcon, actions = actions, windowInsets = windowInsets, windowInsets = safeDrawingWindowInsets, colors = topAppBarColors(), ) } /** * The customized LargeTopAppBar for Settings. */ /** The customized LargeTopAppBar for Settings. */ @OptIn(ExperimentalMaterial3Api::class) @Composable internal fun CustomizedLargeTopAppBar( Loading @@ -124,7 +124,7 @@ internal fun CustomizedLargeTopAppBar( navigationIcon = navigationIcon, actions = actions, colors = topAppBarColors(), windowInsets = windowInsets, windowInsets = safeDrawingWindowInsets, pinnedHeight = ContainerHeight, scrollBehavior = scrollBehavior, ) Loading @@ -134,7 +134,8 @@ internal fun CustomizedLargeTopAppBar( private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) { Text( text = title, modifier = Modifier.padding( modifier = Modifier.padding( start = SettingsDimension.itemPaddingAround, end = SettingsDimension.itemPaddingEnd, ) Loading @@ -145,7 +146,8 @@ private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) { } @Composable private fun topAppBarColors() = TopAppBarColors( private fun topAppBarColors() = TopAppBarColors( containerColor = MaterialTheme.colorScheme.settingsBackground, scrolledContainerColor = MaterialTheme.colorScheme.surfaceVariant, navigationIconContentColor = MaterialTheme.colorScheme.onSurface, Loading @@ -155,17 +157,18 @@ private fun topAppBarColors() = TopAppBarColors( /** * Represents the colors used by a top app bar in different states. * * This implementation animates the container color according to the top app bar scroll state. It * does not animate the leading, headline, or trailing colors. * * @constructor create an instance with arbitrary colors, see [TopAppBarColors] for a * factory method using the default material3 spec * @param containerColor the color used for the background of this BottomAppBar. Use * [Color.Transparent] to have no color. * @param scrolledContainerColor the container color when content is scrolled behind it * @param navigationIconContentColor the content color used for the navigation icon * @param titleContentColor the content color used for the title * @param actionIconContentColor the content color used for actions * @constructor create an instance with arbitrary colors, see [TopAppBarColors] for a factory method * using the default material3 spec */ @Stable private class TopAppBarColors( Loading @@ -180,8 +183,8 @@ private class TopAppBarColors( * Represents the container color used for the top app bar. * * A [colorTransitionFraction] provides a percentage value that can be used to generate a color. * Usually, an app bar implementation will pass in a [colorTransitionFraction] read from * the [TopAppBarState.collapsedFraction] or the [TopAppBarState.overlappedFraction]. * Usually, an app bar implementation will pass in a [colorTransitionFraction] read from the * [TopAppBarState.collapsedFraction] or the [TopAppBarState.overlappedFraction]. * * @param colorTransitionFraction a `0.0` to `1.0` value that represents a color transition * percentage Loading Loading @@ -233,7 +236,8 @@ private fun SingleRowTopAppBar( colors: TopAppBarColors, ) { // Wrap the given actions in a Row. val actionsRow = @Composable { val actionsRow = @Composable { Row( horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, Loading @@ -242,11 +246,16 @@ private fun SingleRowTopAppBar( } // Compose a Surface with a TopAppBarLayout content. Surface(color = colors.scrolledContainerColor) { Box( modifier = Modifier.drawBehind { drawRect(color = colors.scrolledContainerColor) } .semantics { isTraversalGroup = true } .pointerInput(Unit) {} ) { val height = LocalDensity.current.run { ContainerHeight.toPx() } TopAppBarLayout( modifier = Modifier .windowInsetsPadding(windowInsets) modifier = Modifier.windowInsetsPadding(windowInsets) // clip after padding so we don't show the title over the inset area .clipToBounds(), heightPx = height, Loading @@ -255,7 +264,7 @@ private fun SingleRowTopAppBar( actionIconContentColor = colors.actionIconContentColor, title = title, titleTextStyle = titleTextStyle, titleAlpha = 1f, titleAlpha = { 1f }, titleVerticalArrangement = Arrangement.Center, titleBottomPadding = 0, hideTitleSemantics = false, Loading Loading @@ -308,42 +317,40 @@ private fun TwoRowsTopAppBar( // Sets the app bar's height offset limit to hide just the bottom title area and keep top title // visible when collapsed. SideEffect { if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx.floatValue) { scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx.floatValue } } // Obtain the container Color from the TopAppBarColors using the `collapsedFraction`, as the // bottom part of this TwoRowsTopAppBar changes color at the same rate the app bar expands or // collapse. // This will 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?.collapsedFraction ?: 0f val appBarContainerColor = colors.containerColor(colorTransitionFraction) val colorTransitionFraction = { scrollBehavior?.state?.collapsedFraction ?: 0f } val appBarContainerColor = { colors.containerColor(colorTransitionFraction()) } // Wrap the given actions in a Row. val actionsRow = @Composable { val actionsRow = @Composable { Row( horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, content = actions ) } val topTitleAlpha = TopTitleAlphaEasing.transform(colorTransitionFraction) val bottomTitleAlpha = 1f - colorTransitionFraction val topTitleAlpha = { TopTitleAlphaEasing.transform(colorTransitionFraction()) } val bottomTitleAlpha = { 1f - colorTransitionFraction() } // Hide the top row title semantics when its alpha value goes below 0.5 threshold. // Hide the bottom row title semantics when the top title semantics are active. val hideTopRowSemantics = colorTransitionFraction < 0.5f val hideTopRowSemantics by remember(colorTransitionFraction) { derivedStateOf { colorTransitionFraction() < 0.5f } } val hideBottomRowSemantics = !hideTopRowSemantics // Set up support for resizing the top app bar when vertically dragging the bar itself. val appBarDragModifier = if (scrollBehavior != null && !scrollBehavior.isPinned) { val appBarDragModifier = if (scrollBehavior != null && !scrollBehavior.isPinned) { Modifier.draggable( orientation = Orientation.Vertical, state = rememberDraggableState { delta -> scrollBehavior.state.heightOffset += delta }, state = rememberDraggableState { delta -> scrollBehavior.state.heightOffset += delta }, onDragStopped = { velocity -> settleAppBar( scrollBehavior.state, Loading @@ -357,11 +364,18 @@ private fun TwoRowsTopAppBar( Modifier } Surface(modifier = modifier.then(appBarDragModifier), color = appBarContainerColor) { Box( modifier = modifier .then(appBarDragModifier) .drawBehind { drawRect(color = appBarContainerColor()) } .semantics { isTraversalGroup = true } .pointerInput(Unit) {} ) { Column { TopAppBarLayout( modifier = Modifier .windowInsetsPadding(windowInsets) modifier = Modifier.windowInsetsPadding(windowInsets) // clip after padding so we don't show the title over the inset area .clipToBounds(), heightPx = pinnedHeightPx, Loading @@ -378,27 +392,37 @@ private fun TwoRowsTopAppBar( actions = actionsRow, ) TopAppBarLayout( modifier = Modifier // only apply the horizontal sides of the window insets padding, since the top modifier = Modifier // only apply the horizontal sides of the window insets padding, since the // top // padding will always be applied by the layout above .windowInsetsPadding(windowInsets.only(WindowInsetsSides.Horizontal)) .clipToBounds(), heightPx = maxHeightPx.floatValue - pinnedHeightPx + heightPx = maxHeightPx.floatValue - pinnedHeightPx + (scrollBehavior?.state?.heightOffset ?: 0f), navigationIconContentColor = colors.navigationIconContentColor, titleContentColor = colors.titleContentColor, actionIconContentColor = colors.actionIconContentColor, title = { Box(modifier = Modifier.onGloballyPositioned { coordinates -> val measuredMaxHeightPx = density.run { MaxHeightWithoutTitle.toPx() + coordinates.size.height.toFloat() Box( modifier = Modifier.onGloballyPositioned { coordinates -> val measuredMaxHeightPx = density.run { MaxHeightWithoutTitle.toPx() + coordinates.size.height.toFloat() } // Allow larger max height for multi-line title, but do not reduce // max height to prevent flaky. if (measuredMaxHeightPx > defaultMaxHeightPx) { maxHeightPx.floatValue = measuredMaxHeightPx } }) { title() } } ) { title() } }, titleTextStyle = titleTextStyle, titleAlpha = bottomTitleAlpha, Loading Loading @@ -431,10 +455,10 @@ private fun TwoRowsTopAppBar( * @param titleAlpha the title's alpha * @param titleVerticalArrangement the title's vertical arrangement * @param titleBottomPadding the title's bottom padding * @param hideTitleSemantics hides the title node from the semantic tree. Apply this * boolean when this layout is part of a [TwoRowsTopAppBar] to hide the title's semantics * from accessibility services. This is needed to avoid having multiple titles visible to * accessibility services at the same time, when animating between collapsed / expanded states. * @param hideTitleSemantics hides the title node from the semantic tree. Apply this boolean when * this layout is part of a [TwoRowsTopAppBar] to hide the title's semantics from accessibility * services. This is needed to avoid having multiple titles visible to accessibility services at * the same time, when animating between collapsed / expanded states. * @param navigationIcon a navigation icon [Composable] * @param actions actions [Composable] * @param titleScaleDisabled whether the title font scaling is disabled. Default is disabled. Loading @@ -448,7 +472,7 @@ private fun TopAppBarLayout( actionIconContentColor: Color, title: @Composable () -> Unit, titleTextStyle: TextStyle, titleAlpha: Float, titleAlpha: () -> Float, titleVerticalArrangement: Arrangement.Vertical, titleBottomPadding: Int, hideTitleSemantics: Boolean, Loading @@ -458,27 +482,23 @@ private fun TopAppBarLayout( ) { Layout( { Box( Modifier .layoutId("navigationIcon") .padding(start = TopAppBarHorizontalPadding) ) { Box(Modifier.layoutId("navigationIcon").padding(start = TopAppBarHorizontalPadding)) { CompositionLocalProvider( LocalContentColor provides navigationIconContentColor, content = navigationIcon ) } Box( Modifier .layoutId("title") Modifier.layoutId("title") .padding(horizontal = TopAppBarHorizontalPadding) .then(if (hideTitleSemantics) Modifier.clearAndSetSemantics {} else Modifier) .graphicsLayer(alpha = titleAlpha) .graphicsLayer { alpha = titleAlpha() } ) { ProvideTextStyle(value = titleTextStyle) { CompositionLocalProvider( LocalContentColor provides titleContentColor, LocalDensity provides with(LocalDensity.current) { LocalDensity provides with(LocalDensity.current) { Density( density = density, fontScale = if (titleScaleDisabled) 1f else fontScale, Loading @@ -488,11 +508,7 @@ private fun TopAppBarLayout( ) } } Box( Modifier .layoutId("actionIcons") .padding(end = TopAppBarHorizontalPadding) ) { Box(Modifier.layoutId("actionIcons").padding(end = TopAppBarHorizontalPadding)) { CompositionLocalProvider( LocalContentColor provides actionIconContentColor, content = actions Loading @@ -502,20 +518,24 @@ private fun TopAppBarLayout( modifier = modifier ) { measurables, constraints -> val navigationIconPlaceable = measurables.first { it.layoutId == "navigationIcon" } measurables .first { it.layoutId == "navigationIcon" } .measure(constraints.copy(minWidth = 0)) val actionIconsPlaceable = measurables.first { it.layoutId == "actionIcons" } measurables .first { it.layoutId == "actionIcons" } .measure(constraints.copy(minWidth = 0)) val maxTitleWidth = if (constraints.maxWidth == Constraints.Infinity) { val maxTitleWidth = if (constraints.maxWidth == Constraints.Infinity) { constraints.maxWidth } else { (constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width) .coerceAtLeast(0) } val titlePlaceable = measurables.first { it.layoutId == "title" } measurables .first { it.layoutId == "title" } .measure(constraints.copy(minWidth = 0, maxWidth = maxTitleWidth)) // Locate the title's baseline. Loading @@ -538,13 +558,17 @@ private fun TopAppBarLayout( // Title titlePlaceable.placeRelative( x = max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width), y = when (titleVerticalArrangement) { y = when (titleVerticalArrangement) { Arrangement.Center -> (layoutHeight - titlePlaceable.height) / 2 // Apply bottom padding from the title's baseline only when the Arrangement is // "Bottom". // Apply bottom padding from the title's baseline only when the Arrangement // is "Bottom". Arrangement.Bottom -> if (titleBottomPadding == 0) layoutHeight - titlePlaceable.height else layoutHeight - titlePlaceable.height - max( else layoutHeight - titlePlaceable.height - max( 0, titleBottomPadding - titlePlaceable.height + titleBaseline ) Loading @@ -562,7 +586,6 @@ private fun TopAppBarLayout( } } /** * Settles the app bar by flinging, in case the given velocity is greater than zero, and snapping * after the fling settles. Loading Loading @@ -603,9 +626,7 @@ private suspend fun settleAppBar( } // Snap if animation specs were provided. if (snapAnimationSpec != null) { if (state.heightOffset < 0 && state.heightOffset > state.heightOffsetLimit ) { if (state.heightOffset < 0 && state.heightOffset > state.heightOffsetLimit) { AnimationState(initialValue = state.heightOffset).animateTo( if (state.collapsedFraction < 0.5f) { 0f Loading @@ -613,7 +634,9 @@ private suspend fun settleAppBar( state.heightOffsetLimit }, animationSpec = snapAnimationSpec ) { state.heightOffset = value } ) { state.heightOffset = value } } } Loading
packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt +19 −240 File changed.Preview size limit exceeded, changes collapsed. Show changes