1 /* 2 * Copyright 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settingslib.spa.widget.scaffold 18 19 import androidx.compose.animation.core.AnimationSpec 20 import androidx.compose.animation.core.AnimationState 21 import androidx.compose.animation.core.CubicBezierEasing 22 import androidx.compose.animation.core.DecayAnimationSpec 23 import androidx.compose.animation.core.FastOutLinearInEasing 24 import androidx.compose.animation.core.animateDecay 25 import androidx.compose.animation.core.animateTo 26 import androidx.compose.foundation.gestures.Orientation 27 import androidx.compose.foundation.gestures.draggable 28 import androidx.compose.foundation.gestures.rememberDraggableState 29 import androidx.compose.foundation.layout.Arrangement 30 import androidx.compose.foundation.layout.Box 31 import androidx.compose.foundation.layout.Column 32 import androidx.compose.foundation.layout.Row 33 import androidx.compose.foundation.layout.RowScope 34 import androidx.compose.foundation.layout.WindowInsets 35 import androidx.compose.foundation.layout.asPaddingValues 36 import androidx.compose.foundation.layout.navigationBars 37 import androidx.compose.foundation.layout.padding 38 import androidx.compose.foundation.layout.windowInsetsPadding 39 import androidx.compose.material3.ExperimentalMaterial3Api 40 import androidx.compose.material3.LocalContentColor 41 import androidx.compose.material3.MaterialTheme 42 import androidx.compose.material3.ProvideTextStyle 43 import androidx.compose.material3.Surface 44 import androidx.compose.material3.Text 45 import androidx.compose.material3.TopAppBarDefaults 46 import androidx.compose.material3.TopAppBarScrollBehavior 47 import androidx.compose.material3.TopAppBarState 48 import androidx.compose.runtime.Composable 49 import androidx.compose.runtime.CompositionLocalProvider 50 import androidx.compose.runtime.SideEffect 51 import androidx.compose.runtime.Stable 52 import androidx.compose.runtime.getValue 53 import androidx.compose.runtime.mutableFloatStateOf 54 import androidx.compose.runtime.remember 55 import androidx.compose.runtime.rememberUpdatedState 56 import androidx.compose.ui.Alignment 57 import androidx.compose.ui.Modifier 58 import androidx.compose.ui.draw.clipToBounds 59 import androidx.compose.ui.graphics.Color 60 import androidx.compose.ui.graphics.graphicsLayer 61 import androidx.compose.ui.graphics.lerp 62 import androidx.compose.ui.layout.AlignmentLine 63 import androidx.compose.ui.layout.LastBaseline 64 import androidx.compose.ui.layout.Layout 65 import androidx.compose.ui.layout.layoutId 66 import androidx.compose.ui.layout.onGloballyPositioned 67 import androidx.compose.ui.platform.LocalDensity 68 import androidx.compose.ui.semantics.clearAndSetSemantics 69 import androidx.compose.ui.text.TextStyle 70 import androidx.compose.ui.text.style.TextOverflow 71 import androidx.compose.ui.unit.Constraints 72 import androidx.compose.ui.unit.Density 73 import androidx.compose.ui.unit.Dp 74 import androidx.compose.ui.unit.Velocity 75 import androidx.compose.ui.unit.dp 76 import com.android.settingslib.spa.framework.compose.horizontalValues 77 import com.android.settingslib.spa.framework.theme.SettingsDimension 78 import com.android.settingslib.spa.framework.theme.SettingsTheme 79 import kotlin.math.abs 80 import kotlin.math.max 81 import kotlin.math.roundToInt 82 83 @OptIn(ExperimentalMaterial3Api::class) 84 @Composable 85 internal fun CustomizedTopAppBar( 86 title: @Composable () -> Unit, 87 navigationIcon: @Composable () -> Unit = {}, 88 actions: @Composable RowScope.() -> Unit = {}, 89 ) { 90 SingleRowTopAppBar( 91 title = title, 92 titleTextStyle = MaterialTheme.typography.titleMedium, 93 navigationIcon = navigationIcon, 94 actions = actions, 95 windowInsets = TopAppBarDefaults.windowInsets, 96 colors = topAppBarColors(), 97 ) 98 } 99 100 /** 101 * The customized LargeTopAppBar for Settings. 102 */ 103 @OptIn(ExperimentalMaterial3Api::class) 104 @Composable 105 internal fun CustomizedLargeTopAppBar( 106 title: String, 107 modifier: Modifier = Modifier, 108 navigationIcon: @Composable () -> Unit = {}, 109 actions: @Composable RowScope.() -> Unit = {}, 110 scrollBehavior: TopAppBarScrollBehavior? = null, 111 ) { 112 TwoRowsTopAppBar( 113 title = { Title(title = title, maxLines = 3) }, 114 titleTextStyle = MaterialTheme.typography.displaySmall, 115 smallTitleTextStyle = MaterialTheme.typography.titleMedium, 116 titleBottomPadding = LargeTitleBottomPadding, 117 smallTitle = { Title(title = title, maxLines = 1) }, 118 modifier = modifier, 119 navigationIcon = navigationIcon, 120 actions = actions, 121 colors = topAppBarColors(), 122 windowInsets = TopAppBarDefaults.windowInsets, 123 pinnedHeight = ContainerHeight, 124 scrollBehavior = scrollBehavior, 125 ) 126 } 127 128 @Composable 129 private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) { 130 Text( 131 text = title, 132 modifier = Modifier 133 .padding( 134 WindowInsets.navigationBars 135 .asPaddingValues() 136 .horizontalValues() 137 ) 138 .padding( 139 start = SettingsDimension.itemPaddingAround, 140 end = SettingsDimension.itemPaddingEnd, 141 ), 142 overflow = TextOverflow.Ellipsis, 143 maxLines = maxLines, 144 ) 145 } 146 147 @Composable 148 private fun topAppBarColors() = TopAppBarColors( 149 containerColor = MaterialTheme.colorScheme.background, 150 scrolledContainerColor = SettingsTheme.colorScheme.surfaceHeader, 151 navigationIconContentColor = MaterialTheme.colorScheme.onSurface, 152 titleContentColor = MaterialTheme.colorScheme.onSurface, 153 actionIconContentColor = MaterialTheme.colorScheme.onSurfaceVariant, 154 ) 155 156 /** 157 * Represents the colors used by a top app bar in different states. 158 * This implementation animates the container color according to the top app bar scroll state. It 159 * does not animate the leading, headline, or trailing colors. 160 */ 161 @Stable 162 private class TopAppBarColors( 163 val containerColor: Color, 164 val scrolledContainerColor: Color, 165 val navigationIconContentColor: Color, 166 val titleContentColor: Color, 167 val actionIconContentColor: Color, 168 ) { 169 170 /** 171 * Represents the container color used for the top app bar. 172 * 173 * A [colorTransitionFraction] provides a percentage value that can be used to generate a color. 174 * Usually, an app bar implementation will pass in a [colorTransitionFraction] read from 175 * the [TopAppBarState.collapsedFraction] or the [TopAppBarState.overlappedFraction]. 176 * 177 * @param colorTransitionFraction a `0.0` to `1.0` value that represents a color transition 178 * percentage 179 */ 180 @Composable 181 fun containerColor(colorTransitionFraction: Float): Color { 182 return lerp( 183 containerColor, 184 scrolledContainerColor, 185 FastOutLinearInEasing.transform(colorTransitionFraction) 186 ) 187 } 188 189 override fun equals(other: Any?): Boolean { 190 if (this === other) return true 191 if (other == null || other !is TopAppBarColors) return false 192 193 if (containerColor != other.containerColor) return false 194 if (scrolledContainerColor != other.scrolledContainerColor) return false 195 if (navigationIconContentColor != other.navigationIconContentColor) return false 196 if (titleContentColor != other.titleContentColor) return false 197 if (actionIconContentColor != other.actionIconContentColor) return false 198 199 return true 200 } 201 202 override fun hashCode(): Int { 203 var result = containerColor.hashCode() 204 result = 31 * result + scrolledContainerColor.hashCode() 205 result = 31 * result + navigationIconContentColor.hashCode() 206 result = 31 * result + titleContentColor.hashCode() 207 result = 31 * result + actionIconContentColor.hashCode() 208 209 return result 210 } 211 } 212 213 /** 214 * A single-row top app bar that is designed to be called by the small and center aligned top app 215 * bar composables. 216 */ 217 @Composable 218 private fun SingleRowTopAppBar( 219 title: @Composable () -> Unit, 220 titleTextStyle: TextStyle, 221 navigationIcon: @Composable () -> Unit, 222 actions: @Composable (RowScope.() -> Unit), 223 windowInsets: WindowInsets, 224 colors: TopAppBarColors, 225 ) { 226 // Wrap the given actions in a Row. 227 val actionsRow = @Composable { 228 Row( 229 horizontalArrangement = Arrangement.End, 230 verticalAlignment = Alignment.CenterVertically, 231 content = actions 232 ) 233 } 234 235 // Compose a Surface with a TopAppBarLayout content. 236 Surface(color = colors.scrolledContainerColor) { 237 val height = LocalDensity.current.run { ContainerHeight.toPx() } 238 TopAppBarLayout( 239 modifier = Modifier 240 .windowInsetsPadding(windowInsets) 241 // clip after padding so we don't show the title over the inset area 242 .clipToBounds(), 243 heightPx = height, 244 navigationIconContentColor = colors.navigationIconContentColor, 245 titleContentColor = colors.titleContentColor, 246 actionIconContentColor = colors.actionIconContentColor, 247 title = title, 248 titleTextStyle = titleTextStyle, 249 titleAlpha = 1f, 250 titleVerticalArrangement = Arrangement.Center, 251 titleHorizontalArrangement = Arrangement.Start, 252 titleBottomPadding = 0, 253 hideTitleSemantics = false, 254 navigationIcon = navigationIcon, 255 actions = actionsRow, 256 titleScaleDisabled = false, 257 ) 258 } 259 } 260 261 /** 262 * A two-rows top app bar that is designed to be called by the Large and Medium top app bar 263 * composables. 264 * 265 * @throws [IllegalArgumentException] if the given [MaxHeightWithoutTitle] is equal or smaller than 266 * the [pinnedHeight] 267 */ 268 @OptIn(ExperimentalMaterial3Api::class) 269 @Composable 270 private fun TwoRowsTopAppBar( 271 modifier: Modifier = Modifier, 272 title: @Composable () -> Unit, 273 titleTextStyle: TextStyle, 274 titleBottomPadding: Dp, 275 smallTitle: @Composable () -> Unit, 276 smallTitleTextStyle: TextStyle, 277 navigationIcon: @Composable () -> Unit, 278 actions: @Composable RowScope.() -> Unit, 279 windowInsets: WindowInsets, 280 colors: TopAppBarColors, 281 pinnedHeight: Dp, 282 scrollBehavior: TopAppBarScrollBehavior? 283 ) { 284 if (MaxHeightWithoutTitle <= pinnedHeight) { 285 throw IllegalArgumentException( 286 "A TwoRowsTopAppBar max height should be greater than its pinned height" 287 ) 288 } 289 val pinnedHeightPx: Float 290 val titleBottomPaddingPx: Int 291 val defaultMaxHeightPx: Float 292 val density = LocalDensity.current 293 density.run { 294 pinnedHeightPx = pinnedHeight.toPx() 295 titleBottomPaddingPx = titleBottomPadding.roundToPx() 296 defaultMaxHeightPx = (MaxHeightWithoutTitle + DefaultTitleHeight).toPx() 297 } 298 299 val maxHeightPx = remember(density) { mutableFloatStateOf(defaultMaxHeightPx) } 300 301 // Sets the app bar's height offset limit to hide just the bottom title area and keep top title 302 // visible when collapsed. 303 SideEffect { 304 if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx.floatValue) { 305 scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx.floatValue 306 } 307 } 308 309 // Obtain the container Color from the TopAppBarColors using the `collapsedFraction`, as the 310 // bottom part of this TwoRowsTopAppBar changes color at the same rate the app bar expands or 311 // collapse. 312 // This will potentially animate or interpolate a transition between the container color and the 313 // container's scrolled color according to the app bar's scroll state. 314 val colorTransitionFraction = scrollBehavior?.state?.collapsedFraction ?: 0f 315 val appBarContainerColor by rememberUpdatedState(colors.containerColor(colorTransitionFraction)) 316 317 // Wrap the given actions in a Row. 318 val actionsRow = @Composable { 319 Row( 320 horizontalArrangement = Arrangement.End, 321 verticalAlignment = Alignment.CenterVertically, 322 content = actions 323 ) 324 } 325 val topTitleAlpha = TopTitleAlphaEasing.transform(colorTransitionFraction) 326 val bottomTitleAlpha = 1f - colorTransitionFraction 327 // Hide the top row title semantics when its alpha value goes below 0.5 threshold. 328 // Hide the bottom row title semantics when the top title semantics are active. 329 val hideTopRowSemantics = colorTransitionFraction < 0.5f 330 val hideBottomRowSemantics = !hideTopRowSemantics 331 332 // Set up support for resizing the top app bar when vertically dragging the bar itself. 333 val appBarDragModifier = if (scrollBehavior != null && !scrollBehavior.isPinned) { 334 Modifier.draggable( 335 orientation = Orientation.Vertical, 336 state = rememberDraggableState { delta -> 337 scrollBehavior.state.heightOffset = scrollBehavior.state.heightOffset + delta 338 }, 339 onDragStopped = { velocity -> 340 settleAppBar( 341 scrollBehavior.state, 342 velocity, 343 scrollBehavior.flingAnimationSpec, 344 scrollBehavior.snapAnimationSpec 345 ) 346 } 347 ) 348 } else { 349 Modifier 350 } 351 352 Surface(modifier = modifier.then(appBarDragModifier), color = appBarContainerColor) { 353 Column { 354 TopAppBarLayout( 355 modifier = Modifier 356 .windowInsetsPadding(windowInsets) 357 // clip after padding so we don't show the title over the inset area 358 .clipToBounds(), 359 heightPx = pinnedHeightPx, 360 navigationIconContentColor = colors.navigationIconContentColor, 361 titleContentColor = colors.titleContentColor, 362 actionIconContentColor = colors.actionIconContentColor, 363 title = smallTitle, 364 titleTextStyle = smallTitleTextStyle, 365 titleAlpha = topTitleAlpha, 366 titleVerticalArrangement = Arrangement.Center, 367 titleHorizontalArrangement = Arrangement.Start, 368 titleBottomPadding = 0, 369 hideTitleSemantics = hideTopRowSemantics, 370 navigationIcon = navigationIcon, 371 actions = actionsRow, 372 ) 373 TopAppBarLayout( 374 modifier = Modifier.clipToBounds(), 375 heightPx = maxHeightPx.floatValue - pinnedHeightPx + 376 (scrollBehavior?.state?.heightOffset ?: 0f), 377 navigationIconContentColor = colors.navigationIconContentColor, 378 titleContentColor = colors.titleContentColor, 379 actionIconContentColor = colors.actionIconContentColor, 380 title = { 381 Box(modifier = Modifier.onGloballyPositioned { coordinates -> 382 val measuredMaxHeightPx = density.run { 383 MaxHeightWithoutTitle.toPx() + coordinates.size.height.toFloat() 384 } 385 // Allow larger max height for multi-line title, but do not reduce 386 // max height to prevent flaky. 387 if (measuredMaxHeightPx > defaultMaxHeightPx) { 388 maxHeightPx.floatValue = measuredMaxHeightPx 389 } 390 }) { title() } 391 }, 392 titleTextStyle = titleTextStyle, 393 titleAlpha = bottomTitleAlpha, 394 titleVerticalArrangement = Arrangement.Bottom, 395 titleHorizontalArrangement = Arrangement.Start, 396 titleBottomPadding = titleBottomPaddingPx, 397 hideTitleSemantics = hideBottomRowSemantics, 398 navigationIcon = {}, 399 actions = {} 400 ) 401 } 402 } 403 } 404 405 /** 406 * The base [Layout] for all top app bars. This function lays out a top app bar navigation icon 407 * (leading icon), a title (header), and action icons (trailing icons). Note that the navigation and 408 * the actions are optional. 409 * 410 * @param heightPx the total height this layout is capped to 411 * @param navigationIconContentColor the content color that will be applied via a 412 * [LocalContentColor] when composing the navigation icon 413 * @param titleContentColor the color that will be applied via a [LocalContentColor] when composing 414 * the title 415 * @param actionIconContentColor the content color that will be applied via a [LocalContentColor] 416 * when composing the action icons 417 * @param title the top app bar title (header) 418 * @param titleTextStyle the title's text style 419 * @param modifier a [Modifier] 420 * @param titleAlpha the title's alpha 421 * @param titleVerticalArrangement the title's vertical arrangement 422 * @param titleHorizontalArrangement the title's horizontal arrangement 423 * @param titleBottomPadding the title's bottom padding 424 * @param hideTitleSemantics hides the title node from the semantic tree. Apply this 425 * boolean when this layout is part of a [TwoRowsTopAppBar] to hide the title's semantics 426 * from accessibility services. This is needed to avoid having multiple titles visible to 427 * accessibility services at the same time, when animating between collapsed / expanded states. 428 * @param navigationIcon a navigation icon [Composable] 429 * @param actions actions [Composable] 430 * @param titleScaleDisabled whether the title font scaling is disabled. Default is disabled. 431 */ 432 @Composable 433 private fun TopAppBarLayout( 434 modifier: Modifier, 435 heightPx: Float, 436 navigationIconContentColor: Color, 437 titleContentColor: Color, 438 actionIconContentColor: Color, 439 title: @Composable () -> Unit, 440 titleTextStyle: TextStyle, 441 titleAlpha: Float, 442 titleVerticalArrangement: Arrangement.Vertical, 443 titleHorizontalArrangement: Arrangement.Horizontal, 444 titleBottomPadding: Int, 445 hideTitleSemantics: Boolean, 446 navigationIcon: @Composable () -> Unit, 447 actions: @Composable () -> Unit, 448 titleScaleDisabled: Boolean = true, 449 ) { 450 Layout( 451 { 452 Box( 453 Modifier 454 .layoutId("navigationIcon") 455 .padding(start = TopAppBarHorizontalPadding) 456 ) { 457 CompositionLocalProvider( 458 LocalContentColor provides navigationIconContentColor, 459 content = navigationIcon 460 ) 461 } 462 Box( 463 Modifier 464 .layoutId("title") 465 .padding(horizontal = TopAppBarHorizontalPadding) 466 .then(if (hideTitleSemantics) Modifier.clearAndSetSemantics { } else Modifier) 467 .graphicsLayer(alpha = titleAlpha) 468 ) { 469 ProvideTextStyle(value = titleTextStyle) { 470 CompositionLocalProvider( 471 LocalContentColor provides titleContentColor, 472 LocalDensity provides with(LocalDensity.current) { 473 Density( 474 density = density, 475 fontScale = if (titleScaleDisabled) 1f else fontScale, 476 ) 477 }, 478 content = title 479 ) 480 } 481 } 482 Box( 483 Modifier 484 .layoutId("actionIcons") 485 .padding(end = TopAppBarHorizontalPadding) 486 ) { 487 CompositionLocalProvider( 488 LocalContentColor provides actionIconContentColor, 489 content = actions 490 ) 491 } 492 }, 493 modifier = modifier 494 ) { measurables, constraints -> 495 val navigationIconPlaceable = 496 measurables.first { it.layoutId == "navigationIcon" } 497 .measure(constraints.copy(minWidth = 0)) 498 val actionIconsPlaceable = 499 measurables.first { it.layoutId == "actionIcons" } 500 .measure(constraints.copy(minWidth = 0)) 501 502 val maxTitleWidth = if (constraints.maxWidth == Constraints.Infinity) { 503 constraints.maxWidth 504 } else { 505 (constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width) 506 .coerceAtLeast(0) 507 } 508 val titlePlaceable = 509 measurables.first { it.layoutId == "title" } 510 .measure(constraints.copy(minWidth = 0, maxWidth = maxTitleWidth)) 511 512 // Locate the title's baseline. 513 val titleBaseline = 514 if (titlePlaceable[LastBaseline] != AlignmentLine.Unspecified) { 515 titlePlaceable[LastBaseline] 516 } else { 517 0 518 } 519 520 val layoutHeight = if (heightPx.isNaN()) 0 else heightPx.roundToInt() 521 522 layout(constraints.maxWidth, layoutHeight) { 523 // Navigation icon 524 navigationIconPlaceable.placeRelative( 525 x = 0, 526 y = (layoutHeight - navigationIconPlaceable.height) / 2 527 ) 528 529 // Title 530 titlePlaceable.placeRelative( 531 x = when (titleHorizontalArrangement) { 532 Arrangement.Center -> (constraints.maxWidth - titlePlaceable.width) / 2 533 Arrangement.End -> 534 constraints.maxWidth - titlePlaceable.width - actionIconsPlaceable.width 535 // Arrangement.Start. 536 // A TopAppBarTitleInset will make sure the title is offset in case the 537 // navigation icon is missing. 538 else -> max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width) 539 }, 540 y = when (titleVerticalArrangement) { 541 Arrangement.Center -> (layoutHeight - titlePlaceable.height) / 2 542 // Apply bottom padding from the title's baseline only when the Arrangement is 543 // "Bottom". 544 Arrangement.Bottom -> 545 if (titleBottomPadding == 0) layoutHeight - titlePlaceable.height 546 else layoutHeight - titlePlaceable.height - max( 547 0, 548 titleBottomPadding - titlePlaceable.height + titleBaseline 549 ) 550 // Arrangement.Top 551 else -> 0 552 } 553 ) 554 555 // Action icons 556 actionIconsPlaceable.placeRelative( 557 x = constraints.maxWidth - actionIconsPlaceable.width, 558 y = (layoutHeight - actionIconsPlaceable.height) / 2 559 ) 560 } 561 } 562 } 563 564 565 /** 566 * Settles the app bar by flinging, in case the given velocity is greater than zero, and snapping 567 * after the fling settles. 568 */ 569 @OptIn(ExperimentalMaterial3Api::class) 570 private suspend fun settleAppBar( 571 state: TopAppBarState, 572 velocity: Float, 573 flingAnimationSpec: DecayAnimationSpec<Float>?, 574 snapAnimationSpec: AnimationSpec<Float>? 575 ): Velocity { 576 // Check if the app bar is completely collapsed/expanded. If so, no need to settle the app bar, 577 // and just return Zero Velocity. 578 // Note that we don't check for 0f due to float precision with the collapsedFraction 579 // calculation. 580 if (state.collapsedFraction < 0.01f || state.collapsedFraction == 1f) { 581 return Velocity.Zero 582 } 583 var remainingVelocity = velocity 584 // In case there is an initial velocity that was left after a previous user fling, animate to 585 // continue the motion to expand or collapse the app bar. 586 if (flingAnimationSpec != null && abs(velocity) > 1f) { 587 var lastValue = 0f 588 AnimationState( 589 initialValue = 0f, 590 initialVelocity = velocity, 591 ) 592 .animateDecay(flingAnimationSpec) { 593 val delta = value - lastValue 594 val initialHeightOffset = state.heightOffset 595 state.heightOffset = initialHeightOffset + delta 596 val consumed = abs(initialHeightOffset - state.heightOffset) 597 lastValue = value 598 remainingVelocity = this.velocity 599 // avoid rounding errors and stop if anything is unconsumed 600 if (abs(delta - consumed) > 0.5f) this.cancelAnimation() 601 } 602 } 603 // Snap if animation specs were provided. 604 if (snapAnimationSpec != null) { 605 if (state.heightOffset < 0 && 606 state.heightOffset > state.heightOffsetLimit 607 ) { 608 AnimationState(initialValue = state.heightOffset).animateTo( 609 if (state.collapsedFraction < 0.5f) { 610 0f 611 } else { 612 state.heightOffsetLimit 613 }, 614 animationSpec = snapAnimationSpec 615 ) { state.heightOffset = value } 616 } 617 } 618 619 return Velocity(0f, remainingVelocity) 620 } 621 622 // An easing function used to compute the alpha value that is applied to the top title part of a 623 // Medium or Large app bar. 624 private val TopTitleAlphaEasing = CubicBezierEasing(.8f, 0f, .8f, .15f) 625 626 internal val MaxHeightWithoutTitle = 124.dp 627 internal val DefaultTitleHeight = 52.dp 628 internal val ContainerHeight = 56.dp 629 private val LargeTitleBottomPadding = 28.dp 630 private val TopAppBarHorizontalPadding = 4.dp 631 632 // A title inset when the App-Bar is a Medium or Large one. Also used to size a spacer when the 633 // navigation icon is missing. 634 private val TopAppBarTitleInset = 16.dp - TopAppBarHorizontalPadding 635