达到统一和和谐的第三种可能性是创造性的劳动,无论是艺术家还是手工业者的劳动都属于此类劳动。在每一种创造性的劳动中,创造者同他的物质——组成人的周围世界的物质达成一致。无论是木匠做一张桌子,还是金匠打一件首饰,无论是农民种田,还是画家作画——在每一种创造性的劳动中,劳动者和对象合二为一,人在创造的过程中同世界一致。但这一点只适用于自己计划、进行并看到成果的劳动。而在一个职员、一个流水作业线上的工人的现代化工作程序中几乎已经不存在劳动的这种特性。劳动者成为机器或官僚组织的一部分,他不再是“自我”——因为劳动者除了适应社会外,再没有与社会达成一致的可能性。—— 弗洛姆《爱的艺术》
【NowInAndroid架构拆解】系列文章
- 【NowInAndroid架构拆解】(1)分层设计与模块化
- 【NowInAndroid架构拆解】(2)数据层的设计和实现之model与database
- 【NowInAndroid架构拆解】(3)数据层的设计和实现之network
- 【NowInAndroid架构拆解】(4)数据层的设计和实现之data
- 【NowInAndroid架构拆解】(5)VM层的设计和实现之ForYouViewModel
- 【NowInAndroid架构拆解】(6)View层的设计和实现之Navigation路由
- 【NowInAndroid架构拆解】(7)UI层解析——MainActivity构建过程
- 【NowInAndroid架构拆解】(8)UI层解析——ForYou页面展示
- 【NowInAndroid架构拆解】(9)重新审视NowInAndroid架构设计
- 【NowInAndroid架构拆解】番外篇1之Jetpack Compose Navigation
- 【NowInAndroid架构拆解】番外篇2之Bottom Navigation底部导航
- 【NowInAndroid架构拆解】番外篇3之给xml布局者最佳的Jetpack Compose介绍文章
Composable的传递调用
在上一篇文章我们分析到,Activity.onCreate()
中调用setContent{...}
,可创建Compose UI上下文。在这个上下文中调用@Composable
函数,会生成相应的节点,并将其添加到节点树当中。
下面用伪代码分析MainActivity
页面节点树创建过程。
// MainActivity.kt
class MainActivity : ComponentActivity() { // ==> ComponentActivity提供了Lifecycle、ViewModel管理等功能
...
setContent {
...
CompositionLocalProvider(...) {
NiaTheme(...) {
NiaApp(appState) // ==> 层层调用,对于作用域内所有被声明为@Composable的函数,都会建立相应节点
}
}
}
}
// NiaApp.kt
@Composable
fun NiaApp(...) {
NiaBackground(...) {
NiaAGradientBackground(...) {
LaunchedEffect() {...} // ==> 它也是Composable函数
NiaApp(...) // ==> 指向内部另一个Composable函数
}
}
}
@Composable
internal fun NiaApp(...) {
NiaNavigationSuiteScaffold(...) {
Scaffold(...) {
Column(...) {
Box(...) {
NiaNavHost(...) {} ==> @Composable函数层层调用
}
}
}
}
}
// NiaNavHost.kt
@Composable
fun NiaNavHost(...) {
NavHost(...) {
forYourSection(...) { // ==> 连同下面的bookmarksScreen、searchScreen、interestsListDetailScreen,都是NavGraphBuilder的扩展函数,在函数内部生成@Composable节点
topicScreen(...)
}
bookmarksScreen(...)
searchScreen(...)
interestsListDetailScreen()
}
}
在NiaApp中搭建脚手架
internal fun NiaApp()
这个函数,负责构建出包含底部Tab栏在内的整个界面,构建过程是从整体到局部,分层进行的:
- NiaNavigationSuiteScaffold: NIA自定义的脚手架,设置底部Tabs图标、颜色
- Scaffold:
androidx.compose.material3
定义的页面级别脚手架,包含topBar、bottomBar、snackBar、floatingActionButton等支持自定义的属性 - Column、Box: 纵向布局+矩形容器
我认为,这种层层包装的设计,借鉴了Decorator装饰器模式 ,每一层装饰器都增加一部分额外属性,这样化繁为简、化整为零,同时提供重用的能力。
在NiaNavHost中注册路由
前文分析到,在MainActivity.onCreate
函数中,setContent{...}
创建了UI Compose
上下文,在该上下文中,会调用到NavHost()
函数:
// NiaApp.kt
...
NiaNavHost(
appState = appState,
onShowSnackbar = { message, action ->
snackbarHostState.showSnackbar(
message = message,
actionLabel = action,
duration = Short,
) == ActionPerformed
},
)
NiaNavHost
函数的主要功能是将NIA的路由注册到NavController
当中,其中调用了NavGraphBuilder
的几个扩展函数:
@Composable
fun NiaNavHost(
appState: NiaAppState,
onShowSnackbar: suspend (String, String?) -> Boolean,
modifier: Modifier = Modifier,
) {
val navController = appState.navController
NavHost(
navController = navController,
startDestination = ForYouBaseRoute,
modifier = modifier,
) {
forYouSection( // ==> 扩展函数,创建ForYou路由,定义跳转关系,可跳转到Topic页面
onTopicClick = navController::navigateToTopic,
) {
topicScreen(
showBackButton = true,
onBackClick = navController::popBackStack,
onTopicClick = navController::navigateToTopic,
)
}
bookmarksScreen( // ==> 扩展函数,创建Bookmarks路由
onTopicClick = navController::navigateToInterests,
onShowSnackbar = onShowSnackbar,
)
searchScreen( // ==> 扩展函数,创建Search路由,支持跳转到Interests
onBackClick = navController::popBackStack,
onInterestsClick = { appState.navigateToTopLevelDestination(INTERESTS) },
onTopicClick = navController::navigateToInterests,
)
interestsListDetailScreen() // 同样是NavGraphBuilder的扩展函数
}
}
将bookmarksScreen
加入到路由列表
这里以 文章收藏Tab--bookmarksScreen 为例,追溯它是如何加入到路由列表的。
首先,bookmarksScreen()
是作为扩展函数定义在 BookmarksNavigation.kt
文件当中的,在这个文件中同时也创建了路由跳转参数 BookmarksRoute
,用来对参数传递进行强约束。
// BookmarksNavigation.kt
@Serializable object BookmarksRoute // ==> 路由参数,可以包含属性
fun NavController.navigateToBookmarks(navOptions: NavOptions) =
navigate(route = BookmarksRoute, navOptions)
fun NavGraphBuilder.bookmarksScreen( // ==> 作为NavGraphBuilder的扩展函数
onTopicClick: (String) -> Unit,
onShowSnackbar: suspend (String, String?) -> Boolean,
) {
composable<BookmarksRoute> { // ==> 重点!将BookmarksRoute加入路由表
BookmarksRoute(onTopicClick, onShowSnackbar) // ==> 调用了定义在BookmarksScreen.kt中的BookmarksRoute函数
}
}
// BookmarksScreen.kt
@Composable
internal fun BookmarksRoute( // ==> 与参数同名的Composable韩式
onTopicClick: (String) -> Unit,
onShowSnackbar: suspend (String, String?) -> Boolean,
modifier: Modifier = Modifier,
viewModel: BookmarksViewModel = hiltViewModel(),
) {
val feedState by viewModel.feedUiState.collectAsStateWithLifecycle() // ==> 从ViewModel中提取所关注的数据,填充UI
BookmarksScreen(
feedState = feedState,
onShowSnackbar = onShowSnackbar,
removeFromBookmarks = viewModel::removeFromSavedResources,
onNewsResourceViewed = { viewModel.setNewsResourceViewed(it, true) },
onTopicClick = onTopicClick,
modifier = modifier,
shouldDisplayUndoBookmark = viewModel.shouldDisplayUndoBookmark,
undoBookmarkRemoval = viewModel::undoBookmarkRemoval,
clearUndoState = viewModel::clearUndoState,
)
}
NavGraphBuilder.composable 函数
// NavGraphBuilder.kt
public inline fun <reified T : Any> NavGraphBuilder.composable(
typeMap: Map<KType, @JvmSuppressWildcards NavType<*>> = emptyMap(),
deepLinks: List<NavDeepLink> = emptyList(),
...
noinline content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit
) {
destination( // ==> 将创建的ComposeNavigatorDestinationBuilder对象加入路由表
ComposeNavigatorDestinationBuilder(
provider[ComposeNavigator::class],
T::class,
typeMap,
content
)
.apply {
deepLinks.forEach { deepLink -> deepLink(deepLink) }
this.enterTransition = enterTransition
this.exitTransition = exitTransition
this.popEnterTransition = popEnterTransition
this.popExitTransition = popExitTransition
this.sizeTransform = sizeTransform
}
)
}
// ==> 加入路由表
public fun <D : NavDestination> destination(navDestination: NavDestinationBuilder<D>) {
destinations += navDestination.build()
}