mirror of
https://github.com/Joker-x-dev/AndroidProject-Compose.git
synced 2026-02-20 10:48:09 +00:00
优化导航实现
This commit is contained in:
@@ -2,6 +2,7 @@ package com.joker.kit.core.navigation
|
||||
|
||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||
import androidx.compose.animation.SharedTransitionLayout
|
||||
import androidx.compose.animation.SharedTransitionScope
|
||||
import androidx.compose.animation.core.FiniteAnimationSpec
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
@@ -9,6 +10,7 @@ import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
|
||||
@@ -49,7 +51,9 @@ fun AppNavHost(
|
||||
// 创建应用级回退栈,首个页面固定为主页面。
|
||||
val backStack = rememberNavBackStack(MainRoutes.Main)
|
||||
// 基于当前回退栈构建导航控制器,供 AppNavigator 分发命令时使用。
|
||||
val navigationController = rememberBackStackNavigationController(backStack, navigator)
|
||||
val navigationController = remember(backStack, navigator) {
|
||||
createBackStackNavigationController(backStack, navigator)
|
||||
}
|
||||
|
||||
// 在组合生命周期内绑定/解绑导航控制器,确保导航命令总是指向当前有效宿主。
|
||||
DisposableEffect(navigationController) {
|
||||
@@ -77,7 +81,7 @@ fun AppNavHost(
|
||||
transitionSpec = { createForwardTransition() },
|
||||
popTransitionSpec = { createBackwardTransition() },
|
||||
predictivePopTransitionSpec = { createBackwardTransition() },
|
||||
entryProvider = appEntryProvider(),
|
||||
entryProvider = appEntryProvider(this@SharedTransitionLayout),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -118,7 +122,7 @@ private fun createBackwardTransition() = slideInHorizontally(
|
||||
* @return 应用级 EntryProvider
|
||||
* @author Joker.X
|
||||
*/
|
||||
private fun appEntryProvider() = entryProvider {
|
||||
private fun appEntryProvider(scope: SharedTransitionScope) = entryProvider {
|
||||
mainGraph()
|
||||
demoGraph()
|
||||
authGraph()
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.joker.kit.core.navigation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.navigation3.runtime.NavBackStack
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
|
||||
@@ -13,14 +11,11 @@ import androidx.navigation3.runtime.NavKey
|
||||
* @return BackStack 导航控制器
|
||||
* @author Joker.X
|
||||
*/
|
||||
@Composable
|
||||
internal fun rememberBackStackNavigationController(
|
||||
fun createBackStackNavigationController(
|
||||
backStack: NavBackStack<NavKey>,
|
||||
navigator: AppNavigator,
|
||||
): NavigationController {
|
||||
return remember(backStack, navigator) {
|
||||
BackStackNavigationController(backStack = backStack, navigator = navigator)
|
||||
}
|
||||
return BackStackNavigationController(backStack = backStack, navigator = navigator)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,7 +44,11 @@ private class BackStackNavigationController(
|
||||
override fun navigateTo(route: NavKey, navOptions: NavigationOptions?) {
|
||||
val popUpToRoute = navOptions?.popUpToRoute
|
||||
if (popUpToRoute != null) {
|
||||
backStack.popUpTo(route = popUpToRoute, inclusive = navOptions.inclusive)
|
||||
backStack.popUpTo(
|
||||
route = popUpToRoute,
|
||||
inclusive = navOptions.inclusive,
|
||||
allowPopToEmpty = navOptions.allowPopToEmpty,
|
||||
)
|
||||
}
|
||||
backStack.add(route)
|
||||
}
|
||||
@@ -94,9 +93,14 @@ private class BackStackNavigationController(
|
||||
*
|
||||
* @param route 目标路由
|
||||
* @param inclusive 是否包含目标路由
|
||||
* @param allowPopToEmpty 当目标路由是栈底时,是否允许清空整个返回栈
|
||||
* @author Joker.X
|
||||
*/
|
||||
private fun NavBackStack<NavKey>.popUpTo(route: NavKey, inclusive: Boolean) {
|
||||
private fun NavBackStack<NavKey>.popUpTo(
|
||||
route: NavKey,
|
||||
inclusive: Boolean,
|
||||
allowPopToEmpty: Boolean = false,
|
||||
) {
|
||||
val targetIndex = indexOfLast { it == route }
|
||||
if (targetIndex == -1) return
|
||||
|
||||
@@ -104,7 +108,9 @@ private fun NavBackStack<NavKey>.popUpTo(route: NavKey, inclusive: Boolean) {
|
||||
if (removeFromIndex >= size) return
|
||||
|
||||
if (removeFromIndex == 0) {
|
||||
if (size > 1) {
|
||||
if (allowPopToEmpty) {
|
||||
clear()
|
||||
} else if (size > 1) {
|
||||
subList(1, size).clear()
|
||||
}
|
||||
return
|
||||
|
||||
@@ -9,7 +9,7 @@ import androidx.navigation3.runtime.NavKey
|
||||
*
|
||||
* @author Joker.X
|
||||
*/
|
||||
internal interface NavigationController {
|
||||
interface NavigationController {
|
||||
/**
|
||||
* 导航到目标路由
|
||||
*
|
||||
|
||||
@@ -7,9 +7,12 @@ import androidx.navigation3.runtime.NavKey
|
||||
*
|
||||
* @param popUpToRoute 回退栈弹出到的目标路由
|
||||
* @param inclusive 是否包含目标路由本身
|
||||
* @param allowPopToEmpty 是否允许清空整个返回栈(用于替换栈底页面,如 Splash -> Main)
|
||||
* @author Joker.X
|
||||
*/
|
||||
data class NavigationOptions(
|
||||
val popUpToRoute: NavKey? = null,
|
||||
val inclusive: Boolean = false,
|
||||
val allowPopToEmpty: Boolean = false,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.joker.kit.core.navigation
|
||||
|
||||
/**
|
||||
* 公共导航参数定义
|
||||
*
|
||||
* 用于沉淀跨模块可复用的导航参数模型,避免在各业务模块重复声明。
|
||||
*
|
||||
* @author Joker.X
|
||||
*/
|
||||
/**
|
||||
* 通用 ID 参数
|
||||
*
|
||||
* 适用于仅需要传递单个 ID 的页面跳转场景。
|
||||
*
|
||||
* @property id 通用业务 ID
|
||||
* @author Joker.X
|
||||
*/
|
||||
data class IdParam(
|
||||
/**
|
||||
* 通用业务 ID
|
||||
*/
|
||||
val id: Long,
|
||||
)
|
||||
@@ -27,27 +27,24 @@ internal fun LoginRoute(
|
||||
viewModel: LoginViewModel = hiltViewModel()
|
||||
) {
|
||||
LoginScreen(
|
||||
onLoginClick = viewModel::login,
|
||||
onBackClick = ::navigateBack
|
||||
onLoginClick = viewModel::login
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录页面
|
||||
*
|
||||
* @param onBackClick 返回按钮回调
|
||||
* @param onLoginClick 登录按钮回调
|
||||
* @author Joker.X
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun LoginScreen(
|
||||
onBackClick: () -> Unit = {},
|
||||
onLoginClick: () -> Unit = {},
|
||||
) {
|
||||
AppScaffold(
|
||||
titleText = "登录",
|
||||
onBackClick = onBackClick,
|
||||
onBackClick = { navigateBack() },
|
||||
) {
|
||||
LoginContentView(
|
||||
onLoginClick = onLoginClick
|
||||
|
||||
@@ -14,7 +14,6 @@ import javax.inject.Inject
|
||||
/**
|
||||
* 登录页 ViewModel
|
||||
*
|
||||
* @param navigator 导航管理器
|
||||
* @param userState 全局用户状态
|
||||
* @author Joker.X
|
||||
*/
|
||||
|
||||
@@ -75,8 +75,7 @@ internal fun DatabaseRoute(
|
||||
onDescriptionChange = viewModel::onDescriptionChange,
|
||||
onAddClick = viewModel::addItem,
|
||||
onDeleteItem = viewModel::deleteItem,
|
||||
onClearAll = viewModel::clearAll,
|
||||
onBackClick = ::navigateBack
|
||||
onClearAll = viewModel::clearAll
|
||||
)
|
||||
}
|
||||
|
||||
@@ -91,7 +90,6 @@ internal fun DatabaseRoute(
|
||||
* @param onAddClick 点击新增记录
|
||||
* @param onDeleteItem 删除指定记录
|
||||
* @param onClearAll 清空所有记录
|
||||
* @param onBackClick 返回按钮回调
|
||||
* @author Joker.X
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -105,11 +103,10 @@ internal fun DatabaseScreen(
|
||||
onAddClick: () -> Unit = {},
|
||||
onDeleteItem: (Long) -> Unit = {},
|
||||
onClearAll: () -> Unit = {},
|
||||
onBackClick: () -> Unit = {},
|
||||
) {
|
||||
AppScaffold(
|
||||
titleText = "数据库",
|
||||
onBackClick = onBackClick
|
||||
onBackClick = { navigateBack() }
|
||||
) {
|
||||
DatabaseContent(
|
||||
title = title,
|
||||
|
||||
@@ -68,8 +68,7 @@ internal fun LocalStorageRoute(
|
||||
onAvatarChange = viewModel::onAvatarChange,
|
||||
onSaveUser = viewModel::saveUser,
|
||||
onClearUser = viewModel::clearUser,
|
||||
onReloadUser = viewModel::loadUser,
|
||||
onBackClick = ::navigateBack
|
||||
onReloadUser = viewModel::loadUser
|
||||
)
|
||||
}
|
||||
|
||||
@@ -86,7 +85,6 @@ internal fun LocalStorageRoute(
|
||||
* @param onSaveUser 保存用户信息
|
||||
* @param onClearUser 清除用户信息
|
||||
* @param onReloadUser 重新读取用户信息
|
||||
* @param onBackClick 返回按钮回调
|
||||
* @author Joker.X
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -102,11 +100,10 @@ internal fun LocalStorageScreen(
|
||||
onSaveUser: () -> Unit = {},
|
||||
onClearUser: () -> Unit = {},
|
||||
onReloadUser: () -> Unit = {},
|
||||
onBackClick: () -> Unit = {},
|
||||
) {
|
||||
AppScaffold(
|
||||
titleText = "本地存储",
|
||||
onBackClick = onBackClick,
|
||||
onBackClick = { navigateBack() },
|
||||
) {
|
||||
LocalStorageContent(
|
||||
userId = userId,
|
||||
|
||||
@@ -27,7 +27,6 @@ internal fun NavigationResultRoute(
|
||||
viewModel: NavigationResultViewModel = hiltViewModel()
|
||||
) {
|
||||
NavigationResultScreen(
|
||||
onBackClick = ::navigateBack,
|
||||
onSendResult = viewModel::sendResultAndBack
|
||||
)
|
||||
}
|
||||
@@ -36,18 +35,16 @@ internal fun NavigationResultRoute(
|
||||
* 结果回传示例界面
|
||||
*
|
||||
* @param onSendResult 发送结果并返回回调
|
||||
* @param onBackClick 返回按钮回调
|
||||
* @author Joker.X
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun NavigationResultScreen(
|
||||
onBackClick: () -> Unit = {},
|
||||
onSendResult: () -> Unit = {},
|
||||
) {
|
||||
AppScaffold(
|
||||
titleText = "结果回传",
|
||||
onBackClick = onBackClick
|
||||
onBackClick = { navigateBack() }
|
||||
) {
|
||||
NavigationResultContent(onSendResult = onSendResult)
|
||||
}
|
||||
|
||||
@@ -28,8 +28,7 @@ internal fun NavigationWithArgsRoute(
|
||||
)
|
||||
) {
|
||||
NavigationWithArgsScreen(
|
||||
goodsId = viewModel.goodsId,
|
||||
onBackClick = ::navigateBack
|
||||
goodsId = viewModel.goodsId
|
||||
)
|
||||
}
|
||||
|
||||
@@ -37,18 +36,16 @@ internal fun NavigationWithArgsRoute(
|
||||
* 带参跳转示例界面
|
||||
*
|
||||
* @param goodsId 传入的商品 ID
|
||||
* @param onBackClick 返回按钮回调
|
||||
* @author Joker.X
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun NavigationWithArgsScreen(
|
||||
goodsId: Long = 0,
|
||||
onBackClick: () -> Unit = {},
|
||||
) {
|
||||
AppScaffold(
|
||||
titleText = "带参跳转",
|
||||
onBackClick = onBackClick
|
||||
onBackClick = { navigateBack() }
|
||||
) {
|
||||
NavigationWithArgsContent(goodsId = goodsId)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ internal fun NetworkDemoRoute(
|
||||
|
||||
NetworkDemoScreen(
|
||||
uiState = uiState,
|
||||
onBackClick = ::navigateBack,
|
||||
onRetry = viewModel::retryRequest
|
||||
)
|
||||
}
|
||||
@@ -43,7 +42,6 @@ internal fun NetworkDemoRoute(
|
||||
* Network Demo 界面
|
||||
*
|
||||
* @param uiState UI 状态
|
||||
* @param onBackClick 返回按钮回调
|
||||
* @param onRetry 重试回调
|
||||
* @author Joker.X
|
||||
*/
|
||||
@@ -51,12 +49,11 @@ internal fun NetworkDemoRoute(
|
||||
@Composable
|
||||
internal fun NetworkDemoScreen(
|
||||
uiState: BaseNetWorkUiState<Goods> = BaseNetWorkUiState.Loading,
|
||||
onBackClick: () -> Unit = {},
|
||||
onRetry: () -> Unit = {},
|
||||
) {
|
||||
AppScaffold(
|
||||
titleText = "Network Demo",
|
||||
onBackClick = onBackClick,
|
||||
onBackClick = { navigateBack() },
|
||||
) {
|
||||
BaseNetWorkView(
|
||||
uiState = uiState,
|
||||
|
||||
@@ -49,7 +49,6 @@ internal fun NetworkListDemoRoute(
|
||||
onRefresh = viewModel::onRefresh,
|
||||
onLoadMore = viewModel::onLoadMore,
|
||||
shouldTriggerLoadMore = viewModel::shouldTriggerLoadMore,
|
||||
onBackClick = ::navigateBack,
|
||||
onRetry = viewModel::retryRequest,
|
||||
)
|
||||
}
|
||||
@@ -64,7 +63,6 @@ internal fun NetworkListDemoRoute(
|
||||
* @param onRefresh 刷新回调
|
||||
* @param onLoadMore 加载更多回调
|
||||
* @param shouldTriggerLoadMore 是否触发加载更多
|
||||
* @param onBackClick 返回回调
|
||||
* @param onRetry 重试回调
|
||||
* @author Joker.X
|
||||
*/
|
||||
@@ -78,12 +76,11 @@ internal fun NetworkListDemoScreen(
|
||||
onRefresh: () -> Unit = {},
|
||||
onLoadMore: () -> Unit = {},
|
||||
shouldTriggerLoadMore: (lastIndex: Int, totalCount: Int) -> Boolean = { _, _ -> false },
|
||||
onBackClick: () -> Unit = {},
|
||||
onRetry: () -> Unit = {},
|
||||
) {
|
||||
AppScaffold(
|
||||
titleText = "Network List Demo",
|
||||
onBackClick = onBackClick
|
||||
onBackClick = { navigateBack() }
|
||||
) {
|
||||
BaseNetWorkListView(
|
||||
uiState = uiState,
|
||||
|
||||
@@ -38,7 +38,6 @@ internal fun NetworkRequestRoute(
|
||||
|
||||
NetworkRequestScreen(
|
||||
goods = goods,
|
||||
onBackClick = ::navigateBack,
|
||||
onRequestClick = viewModel::onRequestClick
|
||||
)
|
||||
}
|
||||
@@ -47,7 +46,6 @@ internal fun NetworkRequestRoute(
|
||||
* 网络请求示例界面
|
||||
*
|
||||
* @param goods 商品信息
|
||||
* @param onBackClick 返回按钮回调
|
||||
* @param onRequestClick 请求按钮回调
|
||||
* @author Joker.X
|
||||
*/
|
||||
@@ -55,12 +53,11 @@ internal fun NetworkRequestRoute(
|
||||
@Composable
|
||||
internal fun NetworkRequestScreen(
|
||||
goods: Goods? = null,
|
||||
onBackClick: () -> Unit = {},
|
||||
onRequestClick: () -> Unit = {},
|
||||
) {
|
||||
AppScaffold(
|
||||
titleText = "网络请求",
|
||||
onBackClick = onBackClick
|
||||
onBackClick = { navigateBack() }
|
||||
) {
|
||||
NetworkRequestContent(
|
||||
goods = goods,
|
||||
|
||||
@@ -51,8 +51,7 @@ internal fun StateManagementRoute(
|
||||
count = count,
|
||||
onIncrease = viewModel::increase,
|
||||
onDecrease = viewModel::decrease,
|
||||
onReset = viewModel::reset,
|
||||
onBackClick = ::navigateBack
|
||||
onReset = viewModel::reset
|
||||
)
|
||||
}
|
||||
|
||||
@@ -63,7 +62,6 @@ internal fun StateManagementRoute(
|
||||
* @param onIncrease +1 回调
|
||||
* @param onDecrease -1 回调
|
||||
* @param onReset 重置回调
|
||||
* @param onBackClick 返回按钮回调
|
||||
* @author Joker.X
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -73,11 +71,10 @@ internal fun StateManagementScreen(
|
||||
onIncrease: () -> Unit = {},
|
||||
onDecrease: () -> Unit = {},
|
||||
onReset: () -> Unit = {},
|
||||
onBackClick: () -> Unit = {},
|
||||
) {
|
||||
AppScaffold(
|
||||
titleText = "状态管理",
|
||||
onBackClick = onBackClick,
|
||||
onBackClick = { navigateBack() },
|
||||
) {
|
||||
StateManagementContent(
|
||||
count = count,
|
||||
|
||||
@@ -27,27 +27,24 @@ internal fun UserInfoRoute(
|
||||
viewModel: UserInfoViewModel = hiltViewModel()
|
||||
) {
|
||||
UserInfoScreen(
|
||||
onLogoutClick = viewModel::logout,
|
||||
onBackClick = ::navigateBack
|
||||
onLogoutClick = viewModel::logout
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户信息页面
|
||||
*
|
||||
* @param onBackClick 返回按钮回调
|
||||
* @param onLogoutClick 退出登录回调
|
||||
* @author Joker.X
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun UserInfoScreen(
|
||||
onBackClick: () -> Unit = {},
|
||||
onLogoutClick: () -> Unit = {},
|
||||
) {
|
||||
AppScaffold(
|
||||
titleText = "用户信息",
|
||||
onBackClick = onBackClick,
|
||||
onBackClick = { navigateBack() },
|
||||
) {
|
||||
UserInfoContentView(
|
||||
modifier = Modifier.padding(SpacePaddingLarge),
|
||||
|
||||
@@ -12,7 +12,6 @@ import javax.inject.Inject
|
||||
/**
|
||||
* 用户信息页 ViewModel
|
||||
*
|
||||
* @param navigator 导航管理器
|
||||
* @param userState 全局用户状态
|
||||
* @author Joker.X
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user