@@ -27,10 +27,13 @@ import androidx.compose.foundation.layout.padding
2727import androidx.compose.foundation.layout.statusBars
2828import androidx.compose.foundation.layout.windowInsetsPadding
2929import androidx.compose.runtime.Composable
30+ import androidx.compose.runtime.MutableState
3031import androidx.compose.runtime.SideEffect
3132import androidx.compose.runtime.Stable
33+ import androidx.compose.runtime.derivedStateOf
3234import androidx.compose.runtime.getValue
3335import androidx.compose.runtime.mutableFloatStateOf
36+ import androidx.compose.runtime.mutableStateOf
3437import androidx.compose.runtime.remember
3538import androidx.compose.runtime.rememberUpdatedState
3639import androidx.compose.runtime.saveable.Saver
@@ -49,14 +52,15 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
4952import androidx.compose.ui.input.pointer.pointerInput
5053import androidx.compose.ui.layout.Layout
5154import androidx.compose.ui.layout.layoutId
52- import androidx.compose.ui.platform.LocalDensity
5355import androidx.compose.ui.text.font.FontWeight
56+ import androidx.compose.ui.text.style.TextOverflow
5457import androidx.compose.ui.unit.Constraints
5558import androidx.compose.ui.unit.Dp
5659import androidx.compose.ui.unit.IntOffset
5760import androidx.compose.ui.unit.Velocity
5861import androidx.compose.ui.unit.dp
5962import androidx.compose.ui.util.fastFirst
63+ import androidx.compose.ui.util.lerp
6064import top.yukonga.miuix.kmp.basic.TopAppBarState.Companion.Saver
6165import top.yukonga.miuix.kmp.theme.MiuixTheme
6266import kotlin.math.abs
@@ -90,10 +94,13 @@ fun TopAppBar(
9094 defaultWindowInsetsPadding : Boolean = true,
9195 horizontalPadding : Dp = 26.dp
9296) {
93- val density = LocalDensity .current
97+ val largeTitleHeight = remember { mutableStateOf( 0 ) }
9498 val expandedHeightPx by rememberUpdatedState(
95- with (density) { (TopAppBarExpandedHeight ).toPx().coerceAtLeast(0f ) }
99+ remember(largeTitleHeight.value) {
100+ largeTitleHeight.value.toFloat().coerceAtLeast(0f )
101+ }
96102 )
103+
97104 SideEffect {
98105 // Sets the app bar's height offset to collapse the entire bar's height when content is
99106 // scrolled.
@@ -138,6 +145,7 @@ fun TopAppBar(
138145 scrolledOffset = { scrollBehavior?.state?.heightOffset ? : 0f },
139146 expandedHeightPx = expandedHeightPx,
140147 horizontalPadding = horizontalPadding,
148+ largeTitleHeight = largeTitleHeight,
141149 defaultWindowInsetsPadding = defaultWindowInsetsPadding
142150 )
143151 }
@@ -232,9 +240,6 @@ fun MiuixScrollBehavior(
232240 )
233241 }
234242
235- /* * The default expanded height of a [TopAppBar]. */
236- val TopAppBarExpandedHeight : Dp = 48 .dp
237-
238243/* *
239244 * Creates a [TopAppBarState] that is remembered across compositions.
240245 *
@@ -551,8 +556,19 @@ private fun TopAppBarLayout(
551556 scrolledOffset : ScrolledOffset ,
552557 expandedHeightPx : Float ,
553558 horizontalPadding : Dp ,
559+ largeTitleHeight : MutableState <Int >,
554560 defaultWindowInsetsPadding : Boolean
555561) {
562+ // Subtract the scrolledOffset from the maxHeight. The scrolledOffset is expected to be
563+ // equal or smaller than zero.
564+ val heightOffset by remember(scrolledOffset) {
565+ derivedStateOf {
566+ val offset = scrolledOffset.offset()
567+ if (offset.isNaN()) 0 else offset.roundToInt()
568+ }
569+ }
570+
571+ // Small Title Animation
556572 val extOffset = abs(scrolledOffset.offset()) / expandedHeightPx * 2
557573 val alpha by animateFloatAsState(
558574 targetValue = if (1 - extOffset.coerceIn(0f , 1f ) == 0f ) 1f else 0f ,
@@ -562,10 +578,6 @@ private fun TopAppBarLayout(
562578 targetValue = if (extOffset > 1f ) 0f else 10f ,
563579 animationSpec = tween(durationMillis = 250 )
564580 )
565- // Subtract the scrolledOffset from the maxHeight. The scrolledOffset is expected to be
566- // equal or smaller than zero.
567- val scrolledOffsetValue = scrolledOffset.offset()
568- val heightOffset = if (scrolledOffsetValue.isNaN()) 0 else scrolledOffsetValue.roundToInt()
569581
570582 Layout (
571583 {
@@ -580,15 +592,17 @@ private fun TopAppBarLayout(
580592 .layoutId(" title" )
581593 .padding(horizontal = horizontalPadding)
582594 .graphicsLayer(
583- translationY = translationY ,
584- alpha = alpha
595+ alpha = alpha ,
596+ translationY = translationY
585597 )
586598 ) {
587599 Text (
588600 text = title,
589601 maxLines = 1 ,
590602 fontSize = MiuixTheme .textStyles.title3.fontSize,
591- fontWeight = FontWeight .Medium
603+ fontWeight = FontWeight .Medium ,
604+ overflow = TextOverflow .Ellipsis ,
605+ softWrap = false
592606 )
593607 }
594608 Box (
@@ -608,18 +622,17 @@ private fun TopAppBarLayout(
608622 .padding(top = 56 .dp)
609623 .padding(horizontal = horizontalPadding)
610624 .graphicsLayer(alpha = 1f - (abs(scrolledOffset.offset()) / expandedHeightPx * 2 ).coerceIn(0f , 1f ))
611- .clipToBounds()
612625 ) {
613626 Text (
614627 modifier = Modifier .offset { IntOffset (0 , heightOffset) },
615628 text = largeTitle,
616- maxLines = 1 ,
617629 fontSize = MiuixTheme .textStyles.title1.fontSize,
618- fontWeight = FontWeight .Normal
630+ fontWeight = FontWeight .Normal ,
631+ onTextLayout = {
632+ largeTitleHeight.value = it.size.height
633+ }
619634 )
620635 }
621-
622-
623636 }
624637 },
625638 modifier = Modifier
@@ -630,7 +643,6 @@ private fun TopAppBarLayout(
630643 .windowInsetsPadding(WindowInsets .captionBar.only(WindowInsetsSides .Top ))
631644 } else Modifier
632645 )
633- .heightIn(max = 56 .dp + TopAppBarExpandedHeight )
634646 .clipToBounds()
635647 ) { measurables, constraints ->
636648 val navigationIconPlaceable =
@@ -643,36 +655,38 @@ private fun TopAppBarLayout(
643655 .fastFirst { it.layoutId == " actionIcons" }
644656 .measure(constraints.copy(minWidth = 0 , minHeight = 0 ))
645657
646- val maxTitleWidth =
647- if (constraints.maxWidth == Constraints .Infinity ) {
648- constraints.maxWidth
649- } else {
650- (constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width)
651- .coerceAtLeast(0 )
652- }
658+ val maxTitleWidth = constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width
653659
654660 val titlePlaceable =
655661 measurables
656662 .fastFirst { it.layoutId == " title" }
657- .measure(constraints.copy(minWidth = 0 , maxWidth = maxTitleWidth, minHeight = 0 ))
658-
659-
660- val layoutHeight =
661- (if (constraints.maxHeight == Constraints .Infinity ) {
662- constraints.maxHeight
663- } else {
664- constraints.maxHeight + heightOffset
665- }).coerceAtLeast(0 )
663+ .measure(constraints.copy(minWidth = 0 , maxWidth = (maxTitleWidth * 0.9 ).roundToInt(), minHeight = 0 ))
666664
667665 val largeTitlePlaceable =
668666 measurables
669667 .fastFirst { it.layoutId == " largeTitle" }
670668 .measure(
671- constraints.copy(minWidth = 0 , minHeight = 0 )
669+ constraints.copy(
670+ minWidth = 0 ,
671+ minHeight = 0 ,
672+ maxHeight = Constraints .Infinity
673+ )
672674 )
673675
676+ val collapsedHeight = 56 .dp.roundToPx()
677+ val expandedHeight = maxOf(
678+ collapsedHeight,
679+ largeTitlePlaceable.height
680+ )
681+
682+ val layoutHeight = lerp(
683+ start = collapsedHeight,
684+ stop = expandedHeight,
685+ fraction = 1f - (abs(scrolledOffset.offset()) / expandedHeightPx).coerceIn(0f , 1f )
686+ ).toFloat().roundToInt()
687+
674688 layout(constraints.maxWidth, layoutHeight) {
675- val verticalCenter = 60 .dp.roundToPx() / 2
689+ val verticalCenter = collapsedHeight / 2
676690
677691 // Navigation icon
678692 navigationIconPlaceable.placeRelative(
@@ -743,7 +757,9 @@ private fun SmallTopAppBarLayout(
743757 text = title,
744758 maxLines = 1 ,
745759 fontSize = MiuixTheme .textStyles.title3.fontSize,
746- fontWeight = FontWeight .Medium
760+ fontWeight = FontWeight .Medium ,
761+ overflow = TextOverflow .Ellipsis ,
762+ softWrap = false
747763 )
748764 }
749765 Box (
@@ -774,19 +790,12 @@ private fun SmallTopAppBarLayout(
774790 .fastFirst { it.layoutId == " actionIcons" }
775791 .measure(constraints.copy(minWidth = 0 , minHeight = 0 ))
776792
777- val maxTitleWidth =
778- if (constraints.maxWidth == Constraints .Infinity ) {
779- constraints.maxWidth
780- } else {
781- (constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width)
782- .coerceAtLeast(0 )
783- }
793+ val maxTitleWidth = constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width
784794
785795 val titlePlaceable =
786796 measurables
787797 .fastFirst { it.layoutId == " title" }
788- .measure(constraints.copy(minWidth = 0 , maxWidth = maxTitleWidth, minHeight = 0 ))
789-
798+ .measure(constraints.copy(minWidth = 0 , maxWidth = (maxTitleWidth * 0.9 ).roundToInt(), minHeight = 0 ))
790799
791800 val layoutHeight =
792801 if (constraints.maxHeight == Constraints .Infinity ) {
0 commit comments