Skip to content

Commit 1d831f7

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

File tree

2 files changed

+59
-57
lines changed

2 files changed

+59
-57
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: 58 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@ import androidx.compose.animation.core.AnimationSpec
44
import androidx.compose.animation.core.AnimationState
55
import androidx.compose.animation.core.DecayAnimationSpec
66
import androidx.compose.animation.core.animateDecay
7-
import androidx.compose.animation.core.animateFloatAsState
87
import androidx.compose.animation.core.animateTo
98
import androidx.compose.animation.core.spring
10-
import androidx.compose.animation.core.tween
119
import androidx.compose.animation.rememberSplineBasedDecay
1210
import androidx.compose.foundation.gestures.detectVerticalDragGestures
1311
import androidx.compose.foundation.layout.Arrangement
@@ -27,10 +25,13 @@ import androidx.compose.foundation.layout.padding
2725
import androidx.compose.foundation.layout.statusBars
2826
import androidx.compose.foundation.layout.windowInsetsPadding
2927
import androidx.compose.runtime.Composable
28+
import androidx.compose.runtime.MutableState
3029
import androidx.compose.runtime.SideEffect
3130
import androidx.compose.runtime.Stable
31+
import androidx.compose.runtime.derivedStateOf
3232
import androidx.compose.runtime.getValue
3333
import androidx.compose.runtime.mutableFloatStateOf
34+
import androidx.compose.runtime.mutableStateOf
3435
import androidx.compose.runtime.remember
3536
import androidx.compose.runtime.rememberUpdatedState
3637
import androidx.compose.runtime.saveable.Saver
@@ -49,14 +50,15 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
4950
import androidx.compose.ui.input.pointer.pointerInput
5051
import androidx.compose.ui.layout.Layout
5152
import androidx.compose.ui.layout.layoutId
52-
import androidx.compose.ui.platform.LocalDensity
5353
import androidx.compose.ui.text.font.FontWeight
54+
import androidx.compose.ui.text.style.TextOverflow
5455
import androidx.compose.ui.unit.Constraints
5556
import androidx.compose.ui.unit.Dp
5657
import androidx.compose.ui.unit.IntOffset
5758
import androidx.compose.ui.unit.Velocity
5859
import androidx.compose.ui.unit.dp
5960
import androidx.compose.ui.util.fastFirst
61+
import androidx.compose.ui.util.lerp
6062
import top.yukonga.miuix.kmp.basic.TopAppBarState.Companion.Saver
6163
import top.yukonga.miuix.kmp.theme.MiuixTheme
6264
import 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

Comments
 (0)