diff --git a/app/src/main/java/com/example/login_v3/navigation/Main_Screen_tab.kt b/app/src/main/java/com/example/login_v3/navigation/Main_Screen_tab.kt index 1fb1b57..26363ce 100644 --- a/app/src/main/java/com/example/login_v3/navigation/Main_Screen_tab.kt +++ b/app/src/main/java/com/example/login_v3/navigation/Main_Screen_tab.kt @@ -1,7 +1,12 @@ package com.example.login_v3.navigation +import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloat 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.layout.Box 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 androidx.lifecycle.viewmodel.compose.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.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController import com.example.login_v3.navigation.components.AppWallpaperBackground @Composable @@ -102,27 +101,43 @@ fun BottomBarAnimated( height: Dp, content: @Composable BoxScope.() -> Unit ) { - // 🎯 核心修正:讓實體高度也參與動畫 - // 當 visible 為 false 時,這塊 Box 的高度會縮減到 0,從而讓 Scaffold 重新計算 innerPadding - val animatedHeight by animateDpAsState(targetValue = if (visible) height else 0.dp) - - // 位移與透明度動畫保持不變 - val offsetY by animateDpAsState(targetValue = if (visible) 0.dp else height) - val alpha by animateFloatAsState(targetValue = if (visible) 1f else 0f) + // 🎯 高性能優化:使用單一 Transition 管理所有狀態,確保動畫同步 + val transition = updateTransition(targetState = visible, label = "BottomBarTransition") + + // 使用彈簧動畫 (Spring) 達成更流暢且自然的物理回饋感 + val progress by transition.animateFloat( + transitionSpec = { + 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( modifier = Modifier .fillMaxWidth() - .height(animatedHeight) // 關鍵:動態高度決定了內容區域下方的留白 + .height(height * progress) // 只有高度變化會觸發 Layout (為了回收空間) .graphicsLayer { this.alpha = alpha } .background(Color.Transparent) ) { - // 內部的內容容器則維持原高度,並配合 offset 達成滑出效果 + // 🚀 關鍵優化:內部內容使用 translationY,這是 GPU 加速的位移,不會觸發 Layout Pass Box( modifier = Modifier .fillMaxWidth() .height(height) - .offset(y = offsetY) + .graphicsLayer { + translationY = (height.toPx() * (1f - progress)) + } ) { content() } diff --git a/app/src/main/java/com/example/login_v3/navigation/nav_Wheel_display.kt b/app/src/main/java/com/example/login_v3/navigation/nav_Wheel_display.kt index f552e18..5472b5e 100644 --- a/app/src/main/java/com/example/login_v3/navigation/nav_Wheel_display.kt +++ b/app/src/main/java/com/example/login_v3/navigation/nav_Wheel_display.kt @@ -45,6 +45,10 @@ import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.Dp 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.material3.Icon import androidx.compose.material3.MaterialTheme @@ -171,21 +175,27 @@ fun HorizontalWheelPicker( val isInteracting by remember { derivedStateOf { listState.isScrollInProgress } } - // 使用 layoutInfo.visibleItemsInfo 計算最接近中心的 item + // 🎯 性能優化:只有當「中心項」真正改變時,才觸發更新 LaunchedEffect(listState) { - snapshotFlow { listState.layoutInfo.visibleItemsInfo } - .filter { it.isNotEmpty() } - .collect { visibleItems -> - val viewportCenter = - (listState.layoutInfo.viewportStartOffset + listState.layoutInfo.viewportEndOffset) / 2 + snapshotFlow { + val layoutInfo = listState.layoutInfo + val visibleItems = layoutInfo.visibleItemsInfo + if (visibleItems.isEmpty()) return@snapshotFlow -1 - val closest = visibleItems.minByOrNull { vi -> - kotlin.math.abs((vi.offset + vi.size / 2) - viewportCenter) - } ?: visibleItems.first() - - val selectedIndex = closest.key as Int + val viewportCenter = (layoutInfo.viewportStartOffset + layoutInfo.viewportEndOffset) / 2 + + // 找到離中心最近的 Item + val closest = visibleItems.minByOrNull { vi -> + 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]) - } }