@@ -4,10 +4,8 @@ import androidx.compose.animation.core.AnimationSpec
44import androidx.compose.animation.core.AnimationState
55import androidx.compose.animation.core.DecayAnimationSpec
66import androidx.compose.animation.core.animateDecay
7- import androidx.compose.animation.core.animateFloatAsState
87import androidx.compose.animation.core.animateTo
98import androidx.compose.animation.core.spring
10- import androidx.compose.animation.core.tween
119import androidx.compose.animation.rememberSplineBasedDecay
1210import androidx.compose.foundation.gestures.detectVerticalDragGestures
1311import androidx.compose.foundation.layout.Arrangement
@@ -27,10 +25,13 @@ import androidx.compose.foundation.layout.padding
2725import androidx.compose.foundation.layout.statusBars
2826import androidx.compose.foundation.layout.windowInsetsPadding
2927import androidx.compose.runtime.Composable
28+ import androidx.compose.runtime.MutableState
3029import androidx.compose.runtime.SideEffect
3130import androidx.compose.runtime.Stable
31+ import androidx.compose.runtime.derivedStateOf
3232import androidx.compose.runtime.getValue
3333import androidx.compose.runtime.mutableFloatStateOf
34+ import androidx.compose.runtime.mutableStateOf
3435import androidx.compose.runtime.remember
3536import androidx.compose.runtime.rememberUpdatedState
3637import androidx.compose.runtime.saveable.Saver
@@ -49,14 +50,15 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
4950import androidx.compose.ui.input.pointer.pointerInput
5051import androidx.compose.ui.layout.Layout
5152import androidx.compose.ui.layout.layoutId
52- import androidx.compose.ui.platform.LocalDensity
5353import androidx.compose.ui.text.font.FontWeight
54+ import androidx.compose.ui.text.style.TextOverflow
5455import androidx.compose.ui.unit.Constraints
5556import androidx.compose.ui.unit.Dp
5657import androidx.compose.ui.unit.IntOffset
5758import androidx.compose.ui.unit.Velocity
5859import androidx.compose.ui.unit.dp
5960import androidx.compose.ui.util.fastFirst
61+ import androidx.compose.ui.util.lerp
6062import top.yukonga.miuix.kmp.basic.TopAppBarState.Companion.Saver
6163import top.yukonga.miuix.kmp.theme.MiuixTheme
6264import kotlin.math.abs
@@ -90,10 +92,13 @@ fun TopAppBar(
9092 defaultWindowInsetsPadding : Boolean = true,
9193 horizontalPadding : Dp = 26.dp
9294) {
93- val density = LocalDensity .current
95+ val largeTitleHeight = remember { mutableStateOf( 0 ) }
9496 val expandedHeightPx by rememberUpdatedState(
95- with (density) { (TopAppBarExpandedHeight ).toPx().coerceAtLeast(0f ) }
97+ remember(largeTitleHeight.value) {
98+ largeTitleHeight.value.toFloat().coerceAtLeast(0f )
99+ }
96100 )
101+
97102 SideEffect {
98103 // Sets the app bar's height offset to collapse the entire bar's height when content is
99104 // scrolled.
@@ -138,6 +143,7 @@ fun TopAppBar(
138143 scrolledOffset = { scrollBehavior?.state?.heightOffset ? : 0f },
139144 expandedHeightPx = expandedHeightPx,
140145 horizontalPadding = horizontalPadding,
146+ largeTitleHeight = largeTitleHeight,
141147 defaultWindowInsetsPadding = defaultWindowInsetsPadding
142148 )
143149 }
@@ -232,9 +238,6 @@ fun MiuixScrollBehavior(
232238 )
233239 }
234240
235- /* * The default expanded height of a [TopAppBar]. */
236- val TopAppBarExpandedHeight : Dp = 48 .dp
237-
238241/* *
239242 * Creates a [TopAppBarState] that is remembered across compositions.
240243 *
@@ -551,21 +554,23 @@ private fun TopAppBarLayout(
551554 scrolledOffset : ScrolledOffset ,
552555 expandedHeightPx : Float ,
553556 horizontalPadding : Dp ,
557+ largeTitleHeight : MutableState <Int >,
554558 defaultWindowInsetsPadding : Boolean
555559) {
556- val extOffset = abs(scrolledOffset.offset()) / expandedHeightPx * 2
557- val alpha by animateFloatAsState(
558- targetValue = if (1 - extOffset.coerceIn(0f , 1f ) == 0f ) 1f else 0f ,
559- animationSpec = tween(durationMillis = 250 )
560- )
561- val translationY by animateFloatAsState(
562- targetValue = if (extOffset > 1f ) 0f else 10f ,
563- animationSpec = tween(durationMillis = 250 )
564- )
560+ val animatedValues = remember(scrolledOffset.offset(), expandedHeightPx) {
561+ val extOffset = abs(scrolledOffset.offset()) / expandedHeightPx * 2
562+ val translationY = if (extOffset > 1f ) 0f else 10f
563+ val alpha = if (1 - extOffset.coerceIn(0f , 1f ) == 0f ) 1f else 0f
564+ Triple (extOffset, translationY, alpha)
565+ }
565566 // Subtract the scrolledOffset from the maxHeight. The scrolledOffset is expected to be
566567 // equal or smaller than zero.
567- val scrolledOffsetValue = scrolledOffset.offset()
568- val heightOffset = if (scrolledOffsetValue.isNaN()) 0 else scrolledOffsetValue.roundToInt()
568+ val heightOffset by remember(scrolledOffset) {
569+ derivedStateOf {
570+ val offset = scrolledOffset.offset()
571+ if (offset.isNaN()) 0 else offset.roundToInt()
572+ }
573+ }
569574
570575 Layout (
571576 {
@@ -580,15 +585,17 @@ private fun TopAppBarLayout(
580585 .layoutId(" title" )
581586 .padding(horizontal = horizontalPadding)
582587 .graphicsLayer(
583- translationY = translationY ,
584- alpha = alpha
588+ translationY = animatedValues.second ,
589+ alpha = animatedValues.third
585590 )
586591 ) {
587592 Text (
588593 text = title,
589594 maxLines = 1 ,
590595 fontSize = MiuixTheme .textStyles.title3.fontSize,
591- fontWeight = FontWeight .Medium
596+ fontWeight = FontWeight .Medium ,
597+ overflow = TextOverflow .Ellipsis ,
598+ softWrap = false
592599 )
593600 }
594601 Box (
@@ -608,18 +615,17 @@ private fun TopAppBarLayout(
608615 .padding(top = 56 .dp)
609616 .padding(horizontal = horizontalPadding)
610617 .graphicsLayer(alpha = 1f - (abs(scrolledOffset.offset()) / expandedHeightPx * 2 ).coerceIn(0f , 1f ))
611- .clipToBounds()
612618 ) {
613619 Text (
614620 modifier = Modifier .offset { IntOffset (0 , heightOffset) },
615621 text = largeTitle,
616- maxLines = 1 ,
617622 fontSize = MiuixTheme .textStyles.title1.fontSize,
618- fontWeight = FontWeight .Normal
623+ fontWeight = FontWeight .Normal ,
624+ onTextLayout = {
625+ largeTitleHeight.value = it.size.height
626+ }
619627 )
620628 }
621-
622-
623629 }
624630 },
625631 modifier = Modifier
@@ -630,7 +636,6 @@ private fun TopAppBarLayout(
630636 .windowInsetsPadding(WindowInsets .captionBar.only(WindowInsetsSides .Top ))
631637 } else Modifier
632638 )
633- .heightIn(max = 56 .dp + TopAppBarExpandedHeight )
634639 .clipToBounds()
635640 ) { measurables, constraints ->
636641 val navigationIconPlaceable =
@@ -643,36 +648,38 @@ private fun TopAppBarLayout(
643648 .fastFirst { it.layoutId == " actionIcons" }
644649 .measure(constraints.copy(minWidth = 0 , minHeight = 0 ))
645650
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- }
651+ val maxTitleWidth = constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width
653652
654653 val titlePlaceable =
655654 measurables
656655 .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 )
656+ .measure(constraints.copy(minWidth = 0 , maxWidth = (maxTitleWidth * 0.9 ).roundToInt(), minHeight = 0 ))
666657
667658 val largeTitlePlaceable =
668659 measurables
669660 .fastFirst { it.layoutId == " largeTitle" }
670661 .measure(
671- constraints.copy(minWidth = 0 , minHeight = 0 )
662+ constraints.copy(
663+ minWidth = 0 ,
664+ minHeight = 0 ,
665+ maxHeight = Constraints .Infinity
666+ )
672667 )
673668
669+ val collapsedHeight = 56 .dp.roundToPx()
670+ val expandedHeight = maxOf(
671+ collapsedHeight,
672+ largeTitlePlaceable.height
673+ )
674+
675+ val layoutHeight = lerp(
676+ start = collapsedHeight,
677+ stop = expandedHeight,
678+ fraction = 1f - (abs(scrolledOffset.offset()) / expandedHeightPx).coerceIn(0f , 1f )
679+ ).toFloat().roundToInt()
680+
674681 layout(constraints.maxWidth, layoutHeight) {
675- val verticalCenter = 60 .dp.roundToPx() / 2
682+ val verticalCenter = collapsedHeight / 2
676683
677684 // Navigation icon
678685 navigationIconPlaceable.placeRelative(
@@ -743,7 +750,9 @@ private fun SmallTopAppBarLayout(
743750 text = title,
744751 maxLines = 1 ,
745752 fontSize = MiuixTheme .textStyles.title3.fontSize,
746- fontWeight = FontWeight .Medium
753+ fontWeight = FontWeight .Medium ,
754+ overflow = TextOverflow .Ellipsis ,
755+ softWrap = false
747756 )
748757 }
749758 Box (
@@ -774,19 +783,12 @@ private fun SmallTopAppBarLayout(
774783 .fastFirst { it.layoutId == " actionIcons" }
775784 .measure(constraints.copy(minWidth = 0 , minHeight = 0 ))
776785
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- }
786+ val maxTitleWidth = constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width
784787
785788 val titlePlaceable =
786789 measurables
787790 .fastFirst { it.layoutId == " title" }
788- .measure(constraints.copy(minWidth = 0 , maxWidth = maxTitleWidth, minHeight = 0 ))
789-
791+ .measure(constraints.copy(minWidth = 0 , maxWidth = (maxTitleWidth * 0.9 ).roundToInt(), minHeight = 0 ))
790792
791793 val layoutHeight =
792794 if (constraints.maxHeight == Constraints .Infinity ) {
0 commit comments