基础入门
环境配置与依赖
在 Android 项目中集成 Jetpack Compose 需要:
- Android Studio 北极狐(Arctic Fox)或更高版本
- Gradle 7.0+
- minSdkVersion 21+
基础依赖配置(build.gradle):
kotlin
android {
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion "1.4.0"
}
}
dependencies {
implementation "androidx.activity:activity-compose:1.8.0"
implementation platform("androidx.compose:compose-bom:2023.10.01")
implementation "androidx.compose.ui:ui"
implementation "androidx.compose.ui:ui-graphics"
implementation "androidx.compose.ui:ui-tooling-preview"
implementation "androidx.compose.material3:material3"
debugImplementation "androidx.compose.ui:ui-tooling"
}
基本组件与布局
Compose 使用函数式组件构建 UI:
基础组件:
kotlin
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}
// 预览函数
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
Greeting("Android")
}
常用布局:
kotlin
// 垂直排列
Column(modifier = Modifier.padding(16.dp)) {
Text("First item")
Text("Second item")
}
// 水平排列
Row {
Text("Left")
Text("Right")
}
// 可滚动列表
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
// 内容项
}
Modifier 用于设置组件属性:
kotlin
Text(
text = "Styled text",
modifier = Modifier
.padding(16.dp)
.background(Color.LightGray)
.clickable { /* 点击事件 */ }
)
状态管理初步
使用 remember
存储临时状态:
kotlin
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count: $count")
}
}
- remember:保持状态在重组期间不丢失
- mutableStateOf:创建可观察状态,状态变化会触发重组
简单交互实现
处理用户交互:
kotlin
@Composable
fun InteractiveComponent() {
var isSelected by remember { mutableStateOf(false) }
Box(
modifier = Modifier
.size(100.dp)
.background(if (isSelected) Color.Blue else Color.Gray)
.clickable { isSelected = !isSelected }
)
}
常见交互修饰符:
clickable
:点击事件scrollable
:滚动支持draggable
:拖拽支持swipeable
:滑动操作
UI 构建
常用布局组件
Box - 层叠布局:
kotlin
Box(modifier = Modifier.fillMaxSize()) {
Text("Bottom text", modifier = Modifier.align(Alignment.BottomCenter))
Text("Center text", modifier = Modifier.align(Alignment.Center))
}
ConstraintLayout - 约束布局:
kotlin
ConstraintLayout(modifier = Modifier.fillMaxSize()) {
val (button, text) = createRefs()
Button(
onClick = { /* 点击事件 */ },
modifier = Modifier.constrainAs(button) {
top.linkTo(parent.top, margin = 16.dp)
start.linkTo(parent.start)
}
) {
Text("Button")
}
Text(
"Constrained text",
modifier = Modifier.constrainAs(text) {
top.linkTo(button.bottom, margin = 8.dp)
start.linkTo(button.start)
}
)
}
自定义组件开发
创建可复用的自定义组件:
kotlin
@Composable
fun ProfileCard(
name: String,
title: String,
avatar: ImageVector,
modifier: Modifier = Modifier // 提供默认 Modifier
) {
Card(
modifier = modifier
.fillMaxWidth()
.padding(8.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Row(modifier = Modifier.padding(16.dp)) {
Icon(imageVector = avatar, contentDescription = null)
Column(modifier = Modifier.padding(start = 16.dp)) {
Text(text = name, style = MaterialTheme.typography.headlineSmall)
Text(text = title, style = MaterialTheme.typography.bodyLarge)
}
}
}
}
// 使用自定义组件
@Composable
fun ProfileScreen() {
Column {
ProfileCard(
name = "John Doe",
title = "Android Developer",
avatar = Icons.Default.Person
)
// 更多卡片...
}
}
样式与主题应用
Material 3 主题:
kotlin
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = if (darkTheme) {
DarkColorScheme
} else {
LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
// 使用主题
@Composable
fun ThemedComponent() {
AppTheme {
Surface(modifier = Modifier.fillMaxSize()) {
Text(
"Themed text",
style = MaterialTheme.typography.headlineMedium,
color = MaterialTheme.colorScheme.primary
)
}
}
}
自定义样式:
kotlin
val CustomTextStyle = TextStyle(
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
color = Color(0xFF336699)
)
// 使用
Text("Custom styled text", style = CustomTextStyle)
图片与图标处理
加载本地图片:
kotlin
Image(
painter = painterResource(id = R.drawable.ic_launcher),
contentDescription = "Launcher icon",
modifier = Modifier.size(64.dp)
)
网络图片(使用Coil):
kotlin
// 添加依赖
implementation "io.coil-kt:coil-compose:2.4.0"
// 使用
AsyncImage(
model = "https://example.com/image.jpg",
contentDescription = "Network image",
modifier = Modifier.size(128.dp),
contentScale = ContentScale.Crop,
placeholder = painterResource(R.drawable.placeholder)
)
图标:
kotlin
Icon(
imageVector = Icons.Default.Favorite,
contentDescription = "Favorite icon",
tint = Color.Red,
modifier = Modifier.size(24.dp)
)
状态与交互
State 与 MutableState
不可变状态与可变状态:
State<T>
: 只读状态MutableState<T>
: 可修改状态
创建状态的几种方式:
kotlin
// 方式1: by 委托 (推荐)
var count by remember { mutableStateOf(0) }
// 方式2: .value
val countState = remember { mutableStateOf(0) }
countState.value = 1 // 修改值
// 方式3: 使用 rememberSaveable 保存状态(配置变化时保留)
var name by rememberSaveable { mutableStateOf("") }
事件处理机制
Compose 采用单向数据流,通过回调处理事件:
kotlin
// 子组件
@Composable
fun NumberSelector(
value: Int,
onValueChange: (Int) -> Unit, // 事件回调
modifier: Modifier = Modifier
) {
Row(modifier = modifier) {
Button(onClick = { onValueChange(value - 1) }) {
Text("-")
}
Text("$value", modifier = Modifier.padding(8.dp))
Button(onClick = { onValueChange(value + 1) }) {
Text("+")
}
}
}
// 使用
@Composable
fun CounterScreen() {
var count by remember { mutableStateOf(0) }
NumberSelector(
value = count,
onValueChange = { count = it } // 处理事件
)
}
状态提升
状态提升(State Hoisting)是将状态移至父组件的模式:
kotlin
// 无状态组件 (Stateless)
@Composable
fun EmailInput(
email: String, // 从父组件接收状态
onEmailChange: (String) -> Unit, // 通知父组件状态变化
modifier: Modifier = Modifier
) {
TextField(
value = email,
onValueChange = onEmailChange,
label = { Text("Email") },
modifier = modifier
)
}
// 有状态组件 (Stateful)
@Composable
fun LoginScreen() {
var email by rememberSaveable { mutableStateOf("") }
EmailInput(
email = email,
onEmailChange = { email = it }
)
}
状态提升原则:
- 状态由使用它的组件的父组件拥有
- 通过参数传递状态
- 通过回调函数通知状态变化
副作用管理
使用副作用处理 Compose 组件生命周期相关操作:
LaunchedEffect - 组件进入组合时执行:
kotlin
@Composable
fun UserProfile(userId: String) {
var user by remember { mutableStateOf<User?>(null) }
LaunchedEffect(userId) { // 当 userId 变化时重新执行
user = repository.getUser(userId) // suspend 函数调用
}
if (user == null) {
CircularProgressIndicator()
} else {
ProfileContent(user)
}
}
DisposableEffect - 需要清理的副作用:
kotlin
@Composable
fun ConnectivityMonitor() {
val connectivityManager = LocalConnectivityManager.current
DisposableEffect(Unit) {
val callback = object : ConnectivityCallback() {
// 实现回调
}
connectivityManager.registerCallback(callback)
onDispose { // 组件退出组合时执行清理
connectivityManager.unregisterCallback(callback)
}
}
}
rememberCoroutineScope - 获取协程作用域:
kotlin
@Composable
fun MyComponent() {
val scope = rememberCoroutineScope()
var data by remember { mutableStateOf(null) }
Button(onClick = {
scope.launch { // 在按钮点击时启动协程
data = fetchData()
}
}) {
Text("Load data")
}
}
高级特性
动画与过渡效果
属性动画:
kotlin
@Composable
fun AnimatedBox() {
var isExpanded by remember { mutableStateOf(false) }
val size by animateDpAsState(
targetValue = if (isExpanded) 200.dp else 100.dp,
animationSpec = tween(durationMillis = 300, easing = EaseInOut)
)
Box(
modifier = Modifier
.size(size)
.background(Color.Blue)
.clickable { isExpanded = !isExpanded }
)
}
可见性动画:
kotlin
@Composable
fun FadeInOutText(show: Boolean) {
AnimatedVisibility(
visible = show,
enter = fadeIn() + slideInVertically(),
exit = fadeOut() + slideOutVertically()
) {
Text("Animated text")
}
}
状态转换动画:
kotlin
val currentState by remember { mutableStateOf(BoxState.Collapsed) }
Crossfade(targetState = currentState) { state ->
when (state) {
BoxState.Collapsed -> CollapsedBox()
BoxState.Expanded -> ExpandedBox()
}
}
列表与滚动优化
LazyColumn/LazyRow - 高效列表:
kotlin
LazyColumn {
// 单个项目
item {
Header()
}
// 多个项目
items(contacts) { contact ->
ContactItem(contact)
}
// 带索引的项目
itemsIndexed(messages) { index, message ->
MessageItem(message, index)
}
}
列表优化:
kotlin
LazyColumn(
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(
items = largeDataset,
key = { item -> item.id } // 提供稳定的 key 优化性能
) { item ->
// 使用 remember 缓存计算结果
val formattedValue = remember(item.timestamp) {
formatTimestamp(item.timestamp)
}
ListItem(item, formattedValue)
}
}
导航与路由
使用 Jetpack Navigation Compose:
添加依赖:
kotlin
implementation "androidx.navigation:navigation-compose:2.7.5"
基本导航实现:
kotlin
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") {
HomeScreen(
onNavigateToDetails = { id ->
navController.navigate("details/$id")
}
)
}
composable("details/{itemId}") { backStackEntry ->
val itemId = backStackEntry.arguments?.getString("itemId")
DetailsScreen(
itemId = itemId,
onNavigateBack = { navController.popBackStack() }
)
}
}
}
传递参数:
kotlin
// 定义带参数的路由
composable(
route = "user/{userId}?showDetails={showDetails}",
arguments = listOf(
navArgument("userId") { type = NavType.StringType },
navArgument("showDetails") {
type = NavType.BoolType
defaultValue = false
}
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getString("userId")
val showDetails = backStackEntry.arguments?.getBoolean("showDetails")
UserScreen(userId, showDetails)
}
// 导航时传递参数
navController.navigate("user/123?showDetails=true")
与现有视图集成
Compose 中使用传统视图:
kotlin
@Composable
fun MapScreen() {
AndroidView(
factory = { context ->
MapView(context).apply {
// 配置 MapView
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
getMapAsync { map ->
// 配置地图
}
}
},
update = { mapView ->
// 当 Compose 状态变化时更新视图
mapView.onResume()
}
)
}
传统视图中使用 Compose:
kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 在传统布局中添加 Compose 视图
binding.composeContainer.setContent {
MaterialTheme {
ComposeContent()
}
}
}
}
最佳实践
性能优化技巧
避免不必要的重组:
- 使用
remember
缓存计算结果 - 避免在
@Composable
函数中创建对象或 Lambdas - 使用
LaunchedEffect
限制协程执行频率
kotlin
// 错误示例 - 每次重组都会创建新的 Lambda
Column {
items(items) { item ->
Text(
text = item.name,
modifier = Modifier.clickable { /* 新的 Lambda */ }
)
}
}
// 正确示例 - 使用 remember 缓存 Lambda
Column {
val onClick = remember { { /* 只创建一次 */ } }
items(items) { item ->
Text(text = item.name, modifier = Modifier.clickable(onClick = onClick))
}
}
使用 remember
缓存计算:
kotlin
@Composable
fun UserProfile(user: User) {
// 缓存计算结果,仅在 user 变化时重新计算
val initials = remember(user) {
user.name.split(" ").map { it.first() }.joinToString("")
}
Avatar(initials = initials)
}
测试与调试
组件测试:
kotlin
@RunWith(AndroidJUnit4::class)
class MyComposableTest {
@Test
fun myComposable_displaysCorrectText() {
composeTestRule.setContent {
MyComposable(name = "Test")
}
composeTestRule.onNodeWithText("Hello Test")
.assertIsDisplayed()
}
@Test
fun counter_incrementsWhenClicked() {
composeTestRule.setContent {
Counter()
}
composeTestRule.onNodeWithText("0")
.assertIsDisplayed()
composeTestRule.onNodeWithText("Increment")
.performClick()
composeTestRule.onNodeWithText("1")
.assertIsDisplayed()
}
}
调试工具:
- Layout Inspector:检查 Compose 布局层次
- Recomposition Counts:在开发者选项中启用"Show Recomposition Counts"
- Log statements :使用
Log
或println
调试 - remember 调试 :使用
remember { mutableStateOf(value) }
跟踪状态变化
代码组织与复用
推荐的代码组织结构:
bash
ui/
├── components/ # 可复用组件
│ ├── common/ # 通用UI组件
│ ├── forms/ # 表单相关组件
│ └── navigation/ # 导航相关组件
├── screens/ # 完整屏幕
│ ├── home/
│ ├── profile/
│ └── settings/
├── theme/ # 主题和样式
└── navigation/ # 导航配置
组件复用策略:
- 创建小型、专注的组件
- 使用
Modifier
允许父组件自定义布局 - 通过参数提供不同配置
- 使用组合而非继承扩展组件功能
使用 CompositionLocal 传递上下文数据:
kotlin
// 定义 CompositionLocal
val LocalAppSettings = compositionLocalOf<AppSettings> {
error("No AppSettings provided")
}
// 提供值
@Composable
fun AppSettingsProvider(settings: AppSettings, content: @Composable () -> Unit) {
CompositionLocalProvider(LocalAppSettings provides settings) {
content()
}
}
// 在组件中使用
@Composable
fun SettingsDependentComponent() {
val settings = LocalAppSettings.current
Text("Theme: ${settings.theme}")
}
常见问题解决方案
重组过度:
- 使用
remember
缓存计算和对象 - 拆分大型组件为小型组件
- 使用
shouldUpdate
或derivedStateOf
控制重组
状态管理复杂:
- 考虑使用状态容器模式
- 对于复杂应用,考虑使用 ViewModel + StateFlow
- 使用 Hilt 进行依赖注入
处理配置变化:
- 使用
rememberSaveable
保存简单状态 - 使用 ViewModel 保存复杂状态和业务逻辑
- 使用
SavedStateHandle
在 ViewModel 中保存状态
性能问题:
- 使用
LazyColumn
/LazyRow
代替Column
/Row
处理长列表 - 避免在
items
中创建@Composable
函数 - 使用
Modifier.animateContentSize()
代替自定义动画
键盘处理:
kotlin
// 隐藏软键盘
val focusManager = LocalFocusManager.current
Button(onClick = { focusManager.clearFocus() }) {
Text("Hide keyboard")
}
// 自动弹出键盘
LaunchedEffect(Unit) {
focusRequester.requestFocus()
keyboardController.show()
}