dm-padding message-13
This commit is contained in:
parent
d5911ebde1
commit
d682304a3c
2 changed files with 53 additions and 28 deletions
|
|
@ -1,7 +1,12 @@
|
||||||
package com.example.login_v3.navigation
|
package com.example.login_v3.navigation
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.Spring
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
|
import androidx.compose.animation.core.animateFloat
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.spring
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.core.updateTransition
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
|
|
@ -34,13 +39,7 @@ import com.example.login_v3.home.Tg_Server
|
||||||
import com.example.login_v3.home.setting.Tg_Setting
|
import com.example.login_v3.home.setting.Tg_Setting
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.example.login_v3.home.setting.setting_detail_page.viewmodel.Theme_ViewModel
|
import com.example.login_v3.home.setting.setting_detail_page.viewmodel.Theme_ViewModel
|
||||||
|
|
||||||
|
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel // 確保有引入 hiltViewModel
|
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.NavHost
|
|
||||||
import androidx.navigation.compose.composable
|
|
||||||
import androidx.navigation.compose.rememberNavController
|
|
||||||
import com.example.login_v3.navigation.components.AppWallpaperBackground
|
import com.example.login_v3.navigation.components.AppWallpaperBackground
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -102,27 +101,43 @@ fun BottomBarAnimated(
|
||||||
height: Dp,
|
height: Dp,
|
||||||
content: @Composable BoxScope.() -> Unit
|
content: @Composable BoxScope.() -> Unit
|
||||||
) {
|
) {
|
||||||
// 🎯 核心修正:讓實體高度也參與動畫
|
// 🎯 高性能優化:使用單一 Transition 管理所有狀態,確保動畫同步
|
||||||
// 當 visible 為 false 時,這塊 Box 的高度會縮減到 0,從而讓 Scaffold 重新計算 innerPadding
|
val transition = updateTransition(targetState = visible, label = "BottomBarTransition")
|
||||||
val animatedHeight by animateDpAsState(targetValue = if (visible) height else 0.dp)
|
|
||||||
|
// 使用彈簧動畫 (Spring) 達成更流暢且自然的物理回饋感
|
||||||
// 位移與透明度動畫保持不變
|
val progress by transition.animateFloat(
|
||||||
val offsetY by animateDpAsState(targetValue = if (visible) 0.dp else height)
|
transitionSpec = {
|
||||||
val alpha by animateFloatAsState(targetValue = if (visible) 1f else 0f)
|
if (targetState) {
|
||||||
|
// 彈出時帶有一點點的回彈感 (LowBouncy)
|
||||||
|
spring(dampingRatio = Spring.DampingRatioLowBouncy, stiffness = Spring.StiffnessLow)
|
||||||
|
} else {
|
||||||
|
// 隱藏時則較為快速乾脆
|
||||||
|
spring(dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessMediumLow)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label = "progress"
|
||||||
|
) { state -> if (state) 1f else 0f }
|
||||||
|
|
||||||
|
val alpha by transition.animateFloat(
|
||||||
|
transitionSpec = { tween(durationMillis = 250) },
|
||||||
|
label = "alpha"
|
||||||
|
) { state -> if (state) 1f else 0f }
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(animatedHeight) // 關鍵:動態高度決定了內容區域下方的留白
|
.height(height * progress) // 只有高度變化會觸發 Layout (為了回收空間)
|
||||||
.graphicsLayer { this.alpha = alpha }
|
.graphicsLayer { this.alpha = alpha }
|
||||||
.background(Color.Transparent)
|
.background(Color.Transparent)
|
||||||
) {
|
) {
|
||||||
// 內部的內容容器則維持原高度,並配合 offset 達成滑出效果
|
// 🚀 關鍵優化:內部內容使用 translationY,這是 GPU 加速的位移,不會觸發 Layout Pass
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(height)
|
.height(height)
|
||||||
.offset(y = offsetY)
|
.graphicsLayer {
|
||||||
|
translationY = (height.toPx() * (1f - progress))
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,10 @@ import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.yield
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|
@ -171,21 +175,27 @@ fun HorizontalWheelPicker(
|
||||||
|
|
||||||
val isInteracting by remember { derivedStateOf { listState.isScrollInProgress } }
|
val isInteracting by remember { derivedStateOf { listState.isScrollInProgress } }
|
||||||
|
|
||||||
// 使用 layoutInfo.visibleItemsInfo 計算最接近中心的 item
|
// 🎯 性能優化:只有當「中心項」真正改變時,才觸發更新
|
||||||
LaunchedEffect(listState) {
|
LaunchedEffect(listState) {
|
||||||
snapshotFlow { listState.layoutInfo.visibleItemsInfo }
|
snapshotFlow {
|
||||||
.filter { it.isNotEmpty() }
|
val layoutInfo = listState.layoutInfo
|
||||||
.collect { visibleItems ->
|
val visibleItems = layoutInfo.visibleItemsInfo
|
||||||
val viewportCenter =
|
if (visibleItems.isEmpty()) return@snapshotFlow -1
|
||||||
(listState.layoutInfo.viewportStartOffset + listState.layoutInfo.viewportEndOffset) / 2
|
|
||||||
|
|
||||||
val closest = visibleItems.minByOrNull { vi ->
|
val viewportCenter = (layoutInfo.viewportStartOffset + layoutInfo.viewportEndOffset) / 2
|
||||||
kotlin.math.abs((vi.offset + vi.size / 2) - viewportCenter)
|
|
||||||
} ?: visibleItems.first()
|
// 找到離中心最近的 Item
|
||||||
|
val closest = visibleItems.minByOrNull { vi ->
|
||||||
val selectedIndex = closest.key as Int
|
kotlin.math.abs((vi.offset + vi.size / 2) - viewportCenter)
|
||||||
|
} ?: visibleItems.first()
|
||||||
|
|
||||||
|
closest.key as Int
|
||||||
|
}
|
||||||
|
.filter { it != -1 }
|
||||||
|
.distinctUntilChanged() // 關鍵:只有 Index 改變才往下走
|
||||||
|
.collectLatest { selectedIndex ->
|
||||||
|
yield() // 給予 UI 線程呼吸空間,防止快速滑動時卡頓
|
||||||
onValueChange(items[selectedIndex])
|
onValueChange(items[selectedIndex])
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue