正在加载,请稍候…

Android Jetpack Compose 深度探索:状态、动画与性能

全面指南,涵盖 Jetpack Compose 的状态管理(ViewModel)、复杂动画、性能优化以及与 View 系统的互操作性。

Android Jetpack Compose 深度探索:状态、动画与性能

Android Jetpack Compose 深度探索:状态、动画与性能

Jetpack Compose 已成为 Android UI 开发的标准。随着 Compose BOM 2026.x 的发布,稳定性极佳,API 表面丰富。本指南深入探讨状态管理、动画、性能优化以及 View 系统集成的高级模式。

理解组合与状态

Android Jetpack Compose 深度探索:状态、动画与性能 插图

状态提升模式

// 无状态组件 - 易于测试和复用
@Composable
fun EmailInput(
    value: String,
    onValueChange: (String) -> Unit,
    isError: Boolean = false,
    modifier: Modifier = Modifier,
) {
    OutlinedTextField(
        value = value,
        onValueChange = onValueChange,
        label = { Text("Email") },
        isError = isError,
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
        modifier = modifier,
    )
}

使用 StateFlow 的 ViewModel

@HiltViewModel
class LoginViewModel @Inject constructor(
    private val authRepository: AuthRepository,
) : ViewModel() {

    private val _uiState = MutableStateFlow(LoginUiState())
    val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow()

    fun login() {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true) }
            authRepository.login(_uiState.value.email, _uiState.value.password)
                .onSuccess { _uiState.update { it.copy(isLoading = false, loginSuccess = true) } }
                .onFailure { e -> _uiState.update { it.copy(isLoading = false, errorMessage = e.message) } }
        }
    }
}

data class LoginUiState(
    val email: String = "",
    val password: String = "",
    val isLoading: Boolean = false,
    val loginSuccess: Boolean = false,
    val errorMessage: String? = null,
)

高级 Compose 动画

带自定义过渡的 AnimatedVisibility

@Composable
fun NotificationBanner(message: String?, onDismiss: () -> Unit) {
    AnimatedVisibility(
        visible = message != null,
        enter = slideInVertically(
            initialOffsetY = { -it },
            animationSpec = spring(
                dampingRatio = Spring.DampingRatioMediumBouncy,
                stiffness = Spring.StiffnessLow,
            )
        ) + fadeIn(),
        exit = slideOutVertically(targetOffsetY = { -it }) + fadeOut(),
    ) {
        Surface(
            color = MaterialTheme.colorScheme.inverseSurface,
            shape = RoundedCornerShape(12.dp),
            modifier = Modifier.fillMaxWidth().padding(16.dp),
        ) {
            Row(
                modifier = Modifier.padding(16.dp),
                verticalAlignment = Alignment.CenterVertically,
            ) {
                Text(message ?: "", modifier = Modifier.weight(1f))
                IconButton(onClick = onDismiss) {
                    Icon(Icons.Default.Close, "Dismiss")
                }
            }
        }
    }
}

动画化状态

@Composable
fun ProgressButton(progress: Float, onClick: () -> Unit, modifier: Modifier = Modifier) {
    val animatedProgress by animateFloatAsState(
        targetValue = progress,
        animationSpec = tween(durationMillis = 600, easing = FastOutSlowInEasing),
        label = "progress",
    )

    val backgroundColor by animateColorAsState(
        targetValue = if (progress >= 1f)
            MaterialTheme.colorScheme.tertiary
        else
            MaterialTheme.colorScheme.primary,
        label = "background",
    )

    Box(
        modifier = modifier
            .clip(RoundedCornerShape(50))
            .background(backgroundColor)
            .clickable(onClick = onClick)
            .padding(horizontal = 24.dp, vertical = 12.dp),
    ) {
        LinearProgressIndicator(
            progress = { animatedProgress },
            modifier = Modifier.fillMaxWidth().height(4.dp).align(Alignment.BottomCenter),
        )
        Text(
            text = if (progress >= 1f) "Done!" else "${(progress * 100).toInt()}%",
            color = Color.White,
            fontWeight = FontWeight.SemiBold,
        )
    }
}

Android Jetpack Compose 深度探索:状态、动画与性能 插图

性能优化

避免不必要的重组

// 使用 rememberUpdatedState 处理回调
@Composable
fun OptimalExample(count: Int, onCountChanged: (Int) -> Unit) {
    val currentOnCountChanged by rememberUpdatedState(onCountChanged)
    Button(onClick = { currentOnCountChanged(count + 1) }) {
        Text("Count: $count")
    }
}

使用 derivedStateOf 进行昂贵计算

@Composable
fun SortedList(items: List<Item>) {
    val listState = rememberLazyListState()

    val sortedItems by remember(items) {
        derivedStateOf { items.sortedBy { it.priority } }
    }

    val showScrollToTop by remember {
        derivedStateOf { listState.firstVisibleItemIndex > 3 }
    }

    Box {
        LazyColumn(state = listState) {
            items(sortedItems, key = { it.id }) { item ->
                ItemRow(item = item)
            }
        }
        AnimatedVisibility(
            visible = showScrollToTop,
            modifier = Modifier.align(Alignment.BottomEnd),
        ) {
            FloatingActionButton(
                onClick = { /* 滚动到顶部 */ },
                modifier = Modifier.padding(16.dp),
            ) {
                Icon(Icons.Default.KeyboardArrowUp, "Scroll to top")
            }
        }
    }
}

Stable 和 Immutable 注解

@Immutable
data class UserProfile(
    val id: String,
    val name: String,
    val avatarUrl: String,
    val bio: String,
)

@Stable
class CartState {
    var items by mutableStateListOf<CartItem>()
    val total: Double get() = items.sumOf { it.price * it.quantity }
}

Compose 与 View 互操作

Android Jetpack Compose 深度探索:状态、动画与性能 插图

在 Fragment 中嵌入 Compose

class MyFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View {
        return ComposeView(requireContext()).apply {
            setViewCompositionStrategy(
                ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
            )
            setContent {
                MaterialTheme {
                    val viewModel: MyViewModel by viewModels()
                    MyComposableScreen(viewModel = viewModel)
                }
            }
        }
    }
}

在 Compose 中嵌入 Android View

@Composable
fun LegacyMapView(latLng: LatLng, modifier: Modifier = Modifier) {
    val mapView = rememberMapViewWithLifecycle()

    AndroidView(
        factory = { mapView },
        modifier = modifier,
        update = { view ->
            view.getMapAsync { map ->
                map.moveCamera(CameraUpdateFactory.newLatLng(latLng))
            }
        },
    )
}

Lazy 布局优化

@Composable
fun OptimizedFeed(items: List<FeedItem>, onItemClick: (FeedItem) -> Unit) {
    LazyColumn(
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp),
    ) {
        items(
            items = items,
            key = { item -> item.id },
            contentType = { item -> item::class.simpleName },
        ) { item ->
            FeedItemCard(
                item = item,
                onClick = { onItemClick(item) },
                modifier = Modifier.animateItem(),
            )
        }
    }
}

Compose Multiplatform

Compose Multiplatform 支持在 Android、iOS、桌面和 Web 之间共享 UI 代码:

// 共享 UI 代码
@Composable
fun SharedProfileCard(user: User, modifier: Modifier = Modifier) {
    Card(modifier = modifier.fillMaxWidth()) {
        Row(modifier = Modifier.padding(16.dp)) {
            AsyncImage(
                model = user.avatarUrl,
                contentDescription = null,
                modifier = Modifier.size(48.dp).clip(CircleShape),
            )
            Spacer(modifier = Modifier.width(12.dp))
            Column {
                Text(user.name, style = MaterialTheme.typography.titleMedium)
                Text(user.bio, style = MaterialTheme.typography.bodySmall)
            }
        }
    }
}

结论

Jetpack Compose 随着每个版本的发布而不断成熟。掌握状态提升、ViewModel 集成、动画 API 和性能优化,能够帮助你构建美观且响应迅速的 Android 应用。与旧版 View 的互操作故事意味着你可以在现有项目中逐步采用 Compose,而新项目则受益于 Android 历史上最佳的开发者体验。