Skip to content

Commit ad9f0dd

Browse files
committed
library: TopAppBar: largeTitle support multi-line
1 parent a16f352 commit ad9f0dd

File tree

2 files changed

+57
-48
lines changed

2 files changed

+57
-48
lines changed

miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/SearchBar.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ fun SearchBar(
127127
fun InputField(
128128
query: String,
129129
onQueryChange: (String) -> Unit,
130-
label: String = "Search",
130+
label: String = "",
131131
onSearch: (String) -> Unit,
132132
expanded: Boolean,
133133
onExpandedChange: (Boolean) -> Unit,

miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/TopAppBar.kt

Lines changed: 56 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,13 @@ import androidx.compose.foundation.layout.padding
2727
import androidx.compose.foundation.layout.statusBars
2828
import androidx.compose.foundation.layout.windowInsetsPadding
2929
import androidx.compose.runtime.Composable
30+
import androidx.compose.runtime.MutableState
3031
import androidx.compose.runtime.SideEffect
3132
import androidx.compose.runtime.Stable
33+
import androidx.compose.runtime.derivedStateOf
3234
import androidx.compose.runtime.getValue
3335
import androidx.compose.runtime.mutableFloatStateOf
36+
import androidx.compose.runtime.mutableStateOf
3437
import androidx.compose.runtime.remember
3538
import androidx.compose.runtime.rememberUpdatedState
3639
import androidx.compose.runtime.saveable.Saver
@@ -49,14 +52,15 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
4952
import androidx.compose.ui.input.pointer.pointerInput
5053
import androidx.compose.ui.layout.Layout
5154
import androidx.compose.ui.layout.layoutId
52-
import androidx.compose.ui.platform.LocalDensity
5355
import androidx.compose.ui.text.font.FontWeight
56+
import androidx.compose.ui.text.style.TextOverflow
5457
import androidx.compose.ui.unit.Constraints
5558
import androidx.compose.ui.unit.Dp
5659
import androidx.compose.ui.unit.IntOffset
5760
import androidx.compose.ui.unit.Velocity
5861
import androidx.compose.ui.unit.dp
5962
import androidx.compose.ui.util.fastFirst
63+
import androidx.compose.ui.util.lerp
6064
import top.yukonga.miuix.kmp.basic.TopAppBarState.Companion.Saver
6165
import top.yukonga.miuix.kmp.theme.MiuixTheme
6266
import 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

Comments
 (0)