Jetpack Compose多布局实现:状态驱动与自适应UI设计全解析
安卓Compose多布局实现方案
Jetpack Compose中实现多布局的最优方案需要综合考虑代码结构、性能、可维护性和用户体验。以下是经过优化的完整实现方案:
1. 状态管理核心架构
类型安全的状态封装
sealed interface ContentState<out T> {object Loading : ContentState<Nothing>data class Success<T>(val data: T) : ContentState<T>data class Error(val exception: Throwable) : ContentState<Nothing>data class Empty(val message: String? = null) : ContentState<Nothing>
}@Stable
class ScreenState<T>(val state: ContentState<T>,val refresh: () -> Unit,val retry: () -> Unit
)
状态管理ViewModel
class MainViewModel(private val repository: DataRepository
) : ViewModel() {private val _state = mutableStateOf<ContentState<List<Item>>>(ContentState.Loading)val screenState = ScreenState(state = _state.value,refresh = ::loadData,retry = ::loadData)init {loadData()}private fun loadData() {viewModelScope.launch {_state.value = ContentState.Loading_state.value = try {val data = repository.getItems()if (data.isEmpty()) {ContentState.Empty("No items found")} else {ContentState.Success(data)}} catch (e: Exception) {ContentState.Error(e)}}}
}
2. 最优布局实现方案
响应式布局容器
@Composable
fun <T> StatefulLayout(screenState: ScreenState<T>,modifier: Modifier = Modifier,loadingContent: @Composable () -> Unit = { DefaultLoading() },errorContent: @Composable (Throwable, () -> Unit) -> Unit = { e, r -> DefaultError(e, r) },emptyContent: @Composable (String?, () -> Unit) -> Unit = { m, r -> DefaultEmpty(m, r) },successContent: @Composable (T) -> Unit
) {Box(modifier = modifier.fillMaxSize(),contentAlignment = Alignment.Center) {when (val currentState = screenState.state) {is ContentState.Loading -> loadingContent()is ContentState.Error -> errorContent(currentState.exception, screenState.retry)is ContentState.Empty -> emptyContent(currentState.message, screenState.refresh)is ContentState.Success -> successContent(currentState.data)}}
}// 默认组件实现
@Composable
private fun DefaultLoading() {Column(horizontalAlignment = Alignment.CenterHorizontally) {CircularProgressIndicator()Spacer(Modifier.height(16.dp))Text("Loading...")}
}@Composable
private fun DefaultError(exception: Throwable, onRetry: () -> Unit) {Column(horizontalAlignment = Alignment.CenterHorizontally) {Text("Error: ${exception.localizedMessage}", color = MaterialTheme.colorScheme.error)Spacer(Modifier.height(16.dp))Button(onClick = onRetry) {Text("Retry")}}
}
3. 屏幕级实现
主屏幕组合
@Composable
fun MainScreen(viewModel: MainViewModel = viewModel(),onItemClick: (Item) -> Unit
) {val screenState = viewModel.screenStateStatefulLayout(screenState = screenState,successContent = { items ->AdaptiveLayout(items = items,onItemClick = onItemClick)})
}
4. 自适应布局系统
窗口尺寸感知
@Composable
fun AdaptiveLayout(items: List<Item>,onItemClick: (Item) -> Unit
) {val windowSize = rememberWindowSize()when (windowSize) {WindowSize.Compact -> PortraitLayout(items, onItemClick)WindowSize.Medium -> LandscapeLayout(items, onItemClick)WindowSize.Expanded -> LargeScreenLayout(items, onItemClick)}
}enum class WindowSize { Compact, Medium, Expanded }@Composable
fun rememberWindowSize(): WindowSize {val configuration = LocalConfiguration.currentval windowMetrics = remember(configuration) {WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(LocalContext.current)}return remember(windowMetrics) {when {windowMetrics.bounds.width() < 600.dp.value -> WindowSize.CompactwindowMetrics.bounds.width() < 840.dp.value -> WindowSize.Mediumelse -> WindowSize.Expanded}}
}
不同尺寸布局实现
@Composable
private fun PortraitLayout(items: List<Item>,onItemClick: (Item) -> Unit
) {LazyColumn {items(items) { item ->ItemCard(item, onItemClick)}}
}@Composable
private fun LandscapeLayout(items: List<Item>,onItemClick: (Item) -> Unit
) {Row {LazyColumn(Modifier.weight(1f)) {items(items) { item ->ItemCard(item, onItemClick)}}Divider(Modifier.width(1.dp).fillMaxHeight())Box(Modifier.weight(2f)) {Text("Select an item", style = MaterialTheme.typography.headlineMedium)}}
}
5. 性能优化关键点
-
状态最小化更新:
val listState = rememberLazyListState() val showButton by remember {derivedStateOf {listState.firstVisibleItemIndex > 0} }
-
延迟加载:
LazyColumn {items(100) { index ->if (index in listState.layoutInfo.visibleItemsInfo.map { it.index }) {HeavyItem(index)} else {Spacer(Modifier.height(80.dp))}} }
-
重组优化:
@Composable fun ItemCard(item: Item, onItemClick: (Item) -> Unit) {val interactionSource = remember { MutableInteractionSource() }Card(modifier = Modifier.clickable(interactionSource = interactionSource,indication = rememberRipple()) { onItemClick(item) }) {// 内容使用稳定参数ItemContent(item)} }
6. 测试方案
状态测试
@Test
fun `should show loading initially`() = runComposeTest {val mockRepo = mockk<DataRepository>()coEvery { mockRepo.getItems() } coAnswers { delay(1000); emptyList() }setContent {MainScreen(viewModel = MainViewModel(mockRepo))}onNodeWithText("Loading...").assertExists()
}
布局测试
@Test
fun `should show compact layout on small screens`() = runComposeTest {val configuration = Configuration().apply {screenWidthDp = 300}setContent {CompositionLocalProvider(LocalConfiguration provides configuration) {AdaptiveLayout(items = testItems,onItemClick = {})}}// 验证移动端布局元素onNodeWithTag("PortraitLayout").assertExists()
}
最佳实践总结
- 分层架构:清晰分离状态管理、业务逻辑和UI表现
- 类型安全:使用密封类/接口明确所有可能状态
- 响应式设计:根据窗口尺寸自动适配布局
- 性能优化:合理使用派生状态、延迟加载和重组控制
- 可测试性:设计可测试的组件结构和状态管理
- 可定制性:通过参数暴露必要的自定义点
- 一致性:保持不同尺寸布局的交互一致性
这种实现方案提供了高度灵活的多布局支持,同时保持了优秀的性能和可维护性,适用于大多数复杂的Android应用场景。