Skip to content

Commit b4a814c

Browse files
committed
调整下拉组件完成动画
DialogLayout支持自定义动画
1 parent 1fb088b commit b4a814c

File tree

5 files changed

+160
-123
lines changed

5 files changed

+160
-123
lines changed

docs/guide/utils.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ If you use multiple Scaffolds, you need to set the `popupHost` parameter in the
1515
val showDialogState = remember { mutableStateOf(false) }
1616

1717
DialogLayout(
18-
visible = showDialogState
18+
visible = showDialogState, // MutableState<Boolean> to control dialog visibility
19+
enterTransition = fadeIn(), // Optional, custom enter animation for dialog content
20+
exitTransition = fadeOut(), // Optional, custom exit animation for dialog content
21+
dimEnterTransition = fadeIn(), // Optional, custom enter animation for dim layer
22+
dimExitTransition = fadeOut() // Optional, custom exit animation for dim layer
1923
) {
2024
// Dialog content
2125
}

docs/zh_CN/guide/utils.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ Miuix 提供了一系列工具函数,帮助您更高效地开发应用程序
1515
val showDialogState = remember { mutableStateOf(false) }
1616

1717
DialogLayout(
18-
visible = showDialogState
18+
visible = showDialogState, // 控制对话框显示状态
19+
enterTransition = fadeIn(), // 可选,自定义对话框进入动画
20+
exitTransition= fadeOut(), // 可选,自定义对话框对话框退出动画
21+
dimEnterTransition = fadeIn(), // 可选,自定义遮罩层进入动画
22+
dimExitTransition = fadeOut() // 可选,自定义遮罩层退出动画
1923
) {
2024
// 对话框内容
2125
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ fun RefreshHeader(
185185
when (pullToRefreshState.refreshState) {
186186
RefreshState.Idle -> 0f
187187
RefreshState.Pulling -> if (pullToRefreshState.pullProgress > 0.6f) (pullToRefreshState.pullProgress - 0.5f) * 2f else 0f
188-
RefreshState.RefreshComplete -> (1f - refreshCompleteAnimProgress * 1.8f).coerceAtLeast(0f)
188+
RefreshState.RefreshComplete -> (1f - refreshCompleteAnimProgress * 1.95f).coerceAtLeast(0f)
189189
else -> 1f
190190
}
191191
}
@@ -422,11 +422,12 @@ private fun DrawScope.drawRefreshCompleteState(
422422
refreshCompleteProgress: Float
423423
) {
424424
val animatedRadius = radius * ((1f - refreshCompleteProgress).coerceAtLeast(0.9f))
425-
val alphaColor = color.copy(alpha = (1f - refreshCompleteProgress - 0.2f).coerceAtLeast(0f))
425+
val alphaColor = color.copy(alpha = (1f - refreshCompleteProgress - 0.35f).coerceAtLeast(0f))
426+
val y = center.y - radius - strokeWidth + animatedRadius
426427
drawCircle(
427428
color = alphaColor,
428429
radius = animatedRadius,
429-
center = center,
430+
center = Offset(center.x,y),
430431
style = Stroke(strokeWidth, cap = StrokeCap.Round)
431432
)
432433
}

miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperDialog.kt

Lines changed: 76 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ import androidx.compose.runtime.remember
1818
import androidx.compose.runtime.rememberUpdatedState
1919
import androidx.compose.ui.Alignment
2020
import androidx.compose.ui.Modifier
21+
import androidx.compose.ui.draw.clip
2122
import androidx.compose.ui.graphics.Color
22-
import androidx.compose.ui.graphics.graphicsLayer
2323
import androidx.compose.ui.input.pointer.pointerInput
2424
import androidx.compose.ui.platform.LocalDensity
2525
import androidx.compose.ui.text.font.FontWeight
@@ -65,94 +65,88 @@ fun SuperDialog(
6565
defaultWindowInsetsPadding: Boolean = true,
6666
content: @Composable () -> Unit
6767
) {
68-
if (show.value) {
69-
DialogLayout(visible = show) {
70-
val currentContent by rememberUpdatedState(content)
71-
val currentModifier by rememberUpdatedState(modifier)
72-
val currentTitle by rememberUpdatedState(title)
73-
val currentTitleColor by rememberUpdatedState(titleColor)
74-
val currentSummary by rememberUpdatedState(summary)
75-
val currentSummaryColor by rememberUpdatedState(summaryColor)
76-
val currentBackgroundColor by rememberUpdatedState(backgroundColor)
77-
val currentOnDismissRequest by rememberUpdatedState(onDismissRequest)
78-
val currentOutsideMargin by rememberUpdatedState(outsideMargin)
79-
val currentInsideMargin by rememberUpdatedState(insideMargin)
80-
val currentDefaultWindowInsetsPadding by rememberUpdatedState(defaultWindowInsetsPadding)
68+
DialogLayout(show) {
69+
val currentContent by rememberUpdatedState(content)
70+
val currentModifier by rememberUpdatedState(modifier)
71+
val currentTitle by rememberUpdatedState(title)
72+
val currentTitleColor by rememberUpdatedState(titleColor)
73+
val currentSummary by rememberUpdatedState(summary)
74+
val currentSummaryColor by rememberUpdatedState(summaryColor)
75+
val currentBackgroundColor by rememberUpdatedState(backgroundColor)
76+
val currentOnDismissRequest by rememberUpdatedState(onDismissRequest)
77+
val currentOutsideMargin by rememberUpdatedState(outsideMargin)
78+
val currentInsideMargin by rememberUpdatedState(insideMargin)
79+
val currentDefaultWindowInsetsPadding by rememberUpdatedState(defaultWindowInsetsPadding)
8180

82-
val density = LocalDensity.current
83-
val getWindowSize by rememberUpdatedState(getWindowSize())
84-
val windowWidth by rememberUpdatedState(getWindowSize.width.dp / density.density)
85-
val windowHeight by rememberUpdatedState(getWindowSize.height.dp / density.density)
86-
val paddingModifier =
87-
remember(currentOutsideMargin) { Modifier.padding(horizontal = currentOutsideMargin.width).padding(bottom = currentOutsideMargin.height) }
88-
val roundedCorner by rememberUpdatedState(getRoundedCorner())
89-
val bottomCornerRadius by remember { derivedStateOf { if (roundedCorner != 0.dp) roundedCorner - currentOutsideMargin.width else 32.dp } }
90-
val contentAlignment by remember { derivedStateOf { if (windowHeight >= 480.dp && windowWidth >= 840.dp) Alignment.Center else Alignment.BottomCenter } }
81+
val density = LocalDensity.current
82+
val getWindowSize by rememberUpdatedState(getWindowSize())
83+
val windowWidth by rememberUpdatedState(getWindowSize.width.dp / density.density)
84+
val windowHeight by rememberUpdatedState(getWindowSize.height.dp / density.density)
85+
val paddingModifier =
86+
remember(currentOutsideMargin) { Modifier.padding(horizontal = currentOutsideMargin.width).padding(bottom = currentOutsideMargin.height) }
87+
val roundedCorner by rememberUpdatedState(getRoundedCorner())
88+
val bottomCornerRadius by remember { derivedStateOf { if (roundedCorner != 0.dp) roundedCorner - currentOutsideMargin.width else 32.dp } }
89+
val contentAlignment by remember { derivedStateOf { if (windowHeight >= 480.dp && windowWidth >= 840.dp) Alignment.Center else Alignment.BottomCenter } }
9190

92-
Box(
93-
modifier = if (currentDefaultWindowInsetsPadding) {
94-
Modifier
95-
.imePadding()
96-
.navigationBarsPadding()
97-
} else {
98-
Modifier
91+
Box(
92+
modifier = if (currentDefaultWindowInsetsPadding) {
93+
Modifier
94+
.imePadding()
95+
.navigationBarsPadding()
96+
} else {
97+
Modifier
98+
}
99+
.fillMaxSize()
100+
.pointerInput(Unit) {
101+
detectTapGestures(
102+
onTap = {
103+
show.value = false
104+
currentOnDismissRequest?.invoke()
105+
}
106+
)
99107
}
100-
.fillMaxSize()
108+
.then(paddingModifier)
109+
) {
110+
Column(
111+
modifier = currentModifier
112+
.widthIn(max = 420.dp)
101113
.pointerInput(Unit) {
102-
detectTapGestures(
103-
onTap = {
104-
show.value = false
105-
currentOnDismissRequest?.invoke()
106-
}
107-
)
114+
detectTapGestures { /* Do nothing to consume the click */ }
108115
}
109-
.then(paddingModifier)
116+
.align(contentAlignment)
117+
.clip(shape = SmoothRoundedCornerShape(bottomCornerRadius))
118+
.background(
119+
color = currentBackgroundColor,
120+
)
121+
.padding(
122+
horizontal = currentInsideMargin.width,
123+
vertical = currentInsideMargin.height
124+
),
110125
) {
111-
Column(
112-
modifier = currentModifier
113-
.widthIn(max = 420.dp)
114-
.pointerInput(Unit) {
115-
detectTapGestures { /* Do nothing to consume the click */ }
116-
}
117-
.align(contentAlignment)
118-
.graphicsLayer(
119-
shape = SmoothRoundedCornerShape(bottomCornerRadius),
120-
clip = false
121-
)
122-
.background(
123-
color = currentBackgroundColor,
124-
shape = SmoothRoundedCornerShape(bottomCornerRadius)
125-
)
126-
.padding(
127-
horizontal = currentInsideMargin.width,
128-
vertical = currentInsideMargin.height
129-
),
130-
) {
131-
currentTitle?.let {
132-
Text(
133-
modifier = Modifier
134-
.fillMaxWidth()
135-
.padding(bottom = 12.dp),
136-
text = it,
137-
fontSize = MiuixTheme.textStyles.title4.fontSize,
138-
fontWeight = FontWeight.Medium,
139-
textAlign = TextAlign.Center,
140-
color = currentTitleColor
141-
)
142-
}
143-
currentSummary?.let {
144-
Text(
145-
modifier = Modifier
146-
.fillMaxWidth()
147-
.padding(bottom = 12.dp),
148-
text = it,
149-
fontSize = MiuixTheme.textStyles.body1.fontSize,
150-
textAlign = TextAlign.Center,
151-
color = currentSummaryColor
152-
)
153-
}
154-
currentContent()
126+
currentTitle?.let {
127+
Text(
128+
modifier = Modifier
129+
.fillMaxWidth()
130+
.padding(bottom = 12.dp),
131+
text = it,
132+
fontSize = MiuixTheme.textStyles.title4.fontSize,
133+
fontWeight = FontWeight.Medium,
134+
textAlign = TextAlign.Center,
135+
color = currentTitleColor
136+
)
137+
}
138+
currentSummary?.let {
139+
Text(
140+
modifier = Modifier
141+
.fillMaxWidth()
142+
.padding(bottom = 12.dp),
143+
text = it,
144+
fontSize = MiuixTheme.textStyles.body1.fontSize,
145+
textAlign = TextAlign.Center,
146+
color = currentSummaryColor
147+
)
155148
}
149+
currentContent()
156150
}
157151
}
158152
}

miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/MiuixPopupUtils.kt

Lines changed: 70 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package top.yukonga.miuix.kmp.utils
22

33
import androidx.compose.animation.AnimatedVisibility
4+
import androidx.compose.animation.EnterTransition
5+
import androidx.compose.animation.ExitTransition
46
import androidx.compose.animation.core.spring
57
import androidx.compose.animation.core.tween
68
import androidx.compose.animation.fadeIn
@@ -50,28 +52,78 @@ class MiuixPopupUtils {
5052
@Immutable
5153
private data class DialogState(
5254
val content: @Composable () -> Unit,
53-
val zIndex: Float
55+
val zIndex: Float,
56+
val enterTransition: EnterTransition?,
57+
val exitTransition: ExitTransition?,
58+
val dimEnterTransition: EnterTransition?,
59+
val dimExitTransition: ExitTransition?
5460
)
5561

5662
companion object {
5763
private val popupStates = mutableStateMapOf<MutableState<Boolean>, PopupState>()
5864
private val dialogStates = mutableStateMapOf<MutableState<Boolean>, DialogState>()
5965
private var nextZIndex = 1f
6066

61-
private val isAnyPopupShowing by derivedStateOf { popupStates.keys.any { it.value } }
62-
private val isAnyDialogShowing by derivedStateOf { dialogStates.keys.any { it.value } }
67+
private val isAnyPopupShowing by derivedStateOf {
68+
popupStates.isNotEmpty() && popupStates.keys.any { it.value }
69+
}
70+
private val isAnyDialogShowing by derivedStateOf {
71+
dialogStates.isNotEmpty() && dialogStates.keys.any { it.value }
72+
}
73+
74+
private fun defaultMiuixDialogEnterTransition(largeScreen: Boolean = true): EnterTransition {
75+
return if (largeScreen) {
76+
fadeIn(
77+
animationSpec = spring(0.9f, 900f)
78+
) + scaleIn(
79+
initialScale = 0.8f,
80+
animationSpec = spring(0.73f, 900f)
81+
)
82+
} else {
83+
slideInVertically(
84+
initialOffsetY = { fullHeight -> fullHeight },
85+
animationSpec = spring(0.92f, 400f)
86+
)
87+
}
88+
}
89+
90+
private fun defaultMiuixDialogExitTransition(largeScreen: Boolean = true): ExitTransition {
91+
return if (largeScreen) {
92+
fadeOut(
93+
animationSpec = tween(200, easing = DecelerateEasing(1.5f))
94+
) + scaleOut(
95+
targetScale = 0.8f,
96+
animationSpec = tween(200, easing = DecelerateEasing(1.5f))
97+
)
98+
} else {
99+
slideOutVertically(
100+
targetOffsetY = { fullHeight -> fullHeight },
101+
animationSpec = tween(200, easing = DecelerateEasing(1.5f))
102+
)
103+
}
104+
}
63105

64106
/**
65107
* Create a dialog layout.
66108
*
67109
* @param visible The show state controller for this specific dialog.
110+
* @param enterTransition Optional, custom enter animation for dialog content
111+
* @param exitTransition Optional, custom exit animation for dialog content
112+
* @param dimEnterTransition Optional, custom enter animation for dim layer
113+
* @param dimExitTransition Optional, custom exit animation for dim layer
68114
* @param content The [Composable] content of the dialog.
69115
*/
116+
@Composable
70117
@Suppress("FunctionName")
71118
fun DialogLayout(
72-
visible: MutableState<Boolean>,
119+
visible: MutableState<Boolean> = mutableStateOf(true),
120+
enterTransition: EnterTransition? = null,
121+
exitTransition: ExitTransition? = null,
122+
dimEnterTransition: EnterTransition? = null,
123+
dimExitTransition: ExitTransition? = null,
73124
content: (@Composable () -> Unit)? = null,
74125
) {
126+
if (visible.value == false) return
75127
if (content == null) {
76128
if (visible.value) visible.value = false
77129
return
@@ -81,8 +133,14 @@ class MiuixPopupUtils {
81133
} else {
82134
dialogStates[visible]?.zIndex ?: nextZIndex++
83135
}
84-
dialogStates[visible] = DialogState(content, currentZIndex)
85-
if (!visible.value) visible.value = true
136+
dialogStates[visible] = DialogState({
137+
content() },
138+
currentZIndex,
139+
enterTransition,
140+
exitTransition,
141+
dimEnterTransition,
142+
dimExitTransition
143+
)
86144
}
87145

88146
/**
@@ -152,8 +210,8 @@ class MiuixPopupUtils {
152210
AnimatedVisibility(
153211
visible = internalVisible,
154212
modifier = Modifier.zIndex(dialogState.zIndex).fillMaxSize(),
155-
enter = fadeIn(animationSpec = tween(dimEnterDuration, easing = DecelerateEasing(1.5f))),
156-
exit = fadeOut(animationSpec = tween(dimExitDuration, easing = DecelerateEasing(1.5f)))
213+
enter = dialogState.dimEnterTransition?:fadeIn(animationSpec = tween(dimEnterDuration, easing = DecelerateEasing(1.5f))),
214+
exit = dialogState.dimExitTransition?:fadeOut(animationSpec = tween(dimExitDuration, easing = DecelerateEasing(1.5f)))
157215
) {
158216
Box(
159217
modifier = Modifier
@@ -166,32 +224,8 @@ class MiuixPopupUtils {
166224
AnimatedVisibility(
167225
visible = internalVisible,
168226
modifier = Modifier.zIndex(dialogState.zIndex).fillMaxSize(),
169-
enter = if (largeScreen) {
170-
fadeIn(
171-
animationSpec = spring(0.9f, 900f)
172-
) + scaleIn(
173-
initialScale = 0.8f,
174-
animationSpec = spring(0.73f, 900f)
175-
)
176-
} else {
177-
slideInVertically(
178-
initialOffsetY = { fullHeight -> fullHeight },
179-
animationSpec = spring(0.92f, 400f)
180-
)
181-
},
182-
exit = if (largeScreen) {
183-
fadeOut(
184-
animationSpec = tween(200, easing = DecelerateEasing(1.5f))
185-
) + scaleOut(
186-
targetScale = 0.8f,
187-
animationSpec = tween(200, easing = DecelerateEasing(1.5f))
188-
)
189-
} else {
190-
slideOutVertically(
191-
targetOffsetY = { fullHeight -> fullHeight },
192-
animationSpec = tween(200, easing = DecelerateEasing(1.5f))
193-
)
194-
}
227+
enter = dialogState.enterTransition?:defaultMiuixDialogEnterTransition(largeScreen),
228+
exit = dialogState.exitTransition?:defaultMiuixDialogExitTransition(largeScreen)
195229
) {
196230
Box(
197231
modifier = Modifier.fillMaxSize()
@@ -264,10 +298,10 @@ class MiuixPopupUtils {
264298
popupState.content()
265299
}
266300

267-
DisposableEffect(showState) {
301+
DisposableEffect(showState.value) {
268302
onDispose {
269303
if (!showState.value) {
270-
popupStates.remove(showState)
304+
dialogStates.remove(showState)
271305
}
272306
}
273307
}

0 commit comments

Comments
 (0)