Android Compose 自定义组件完全指南
前言
Jetpack Compose 作为 Android 的现代 UI 工具包,提供了强大的自定义组件能力。通过自定义组件,我们可以创建可重用、可维护的 UI 元素,提升开发效率和代码质量。本文将详细介绍如何在 Compose 中创建和使用自定义组件。
基础概念
在 Compose 中,自定义组件本质上就是一个带有 @Composable
注解的函数。这些函数可以组合其他 Composable 函数来创建新的 UI 元素。
@Composable
fun MyCustomComponent() {Text("这是一个自定义组件")
}
简单自定义组件
让我们从一个简单的自定义按钮开始:
@Composable
fun CustomButton() {Button(onClick = { },colors = ButtonDefaults.buttonColors(containerColor = Color.Blue),modifier = Modifier.padding(16.dp).fillMaxWidth()) {Text(text = "点击我",color = Color.White,fontSize = 16.sp)}
}// 使用方式
@Composable
fun MainScreen() {Column {CustomButton()CustomButton()}
}
带参数的自定义组件
为了让组件更加灵活,我们可以添加参数:
@Composable
fun CustomButton(text: String,onClick: () -> Unit,backgroundColor: Color = Color.Blue,textColor: Color = Color.White,modifier: Modifier = Modifier
) {Button(onClick = onClick,colors = ButtonDefaults.buttonColors(containerColor = backgroundColor),modifier = modifier.padding(8.dp)) {Text(text = text,color = textColor,fontSize = 16.sp)}
}// 使用方式
@Composable
fun MainScreen() {Column {CustomButton(text = "确认",onClick = { /* 处理点击 */ },backgroundColor = Color.Green)CustomButton(text = "取消",onClick = { /* 处理点击 */ },backgroundColor = Color.Red)}
}
状态管理
内部状态管理
对于需要维护内部状态的组件,可以使用 remember
和 mutableStateOf
:
@Composable
fun Counter() {var count by remember { mutableStateOf(0) }Column(horizontalAlignment = Alignment.CenterHorizontally,modifier = Modifier.padding(16.dp)) {Text(text = "计数: $count",fontSize = 20.sp,modifier = Modifier.padding(bottom = 16.dp))Row {Button(onClick = { count++ }) {Text("增加")}Spacer(modifier = Modifier.width(8.dp))Button(onClick = { count-- }) {Text("减少")}}}
}
状态提升
更好的做法是将状态提升到父组件:
@Composable
fun Counter(count: Int,onIncrement: () -> Unit,onDecrement: () -> Unit
) {Column(horizontalAlignment = Alignment.CenterHorizontally,modifier = Modifier.padding(16.dp)) {Text(text = "计数: $count",fontSize = 20.sp,modifier = Modifier.padding(bottom = 16.dp))Row {Button(onClick = onIncrement) {Text("增加")}Spacer(modifier = Modifier.width(8.dp))Button(onClick = onDecrement) {Text("减少")}}}
}@Composable
fun MainScreen() {var count by remember { mutableStateOf(0) }Counter(count = count,onIncrement = { count++ },onDecrement = { count-- })
}
复杂自定义组件
让我们创建一个更复杂的组件 - 用户卡片:
data class User(val id: Int,val name: String,val email: String,val avatarUrl: String = ""
)@Composable
fun UserCard(user: User,onUserClick: (User) -> Unit,modifier: Modifier = Modifier
) {Card(modifier = modifier.fillMaxWidth().padding(8.dp).clickable { onUserClick(user) },elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)) {Row(modifier = Modifier.fillMaxWidth().padding(16.dp),verticalAlignment = Alignment.CenterVertically) {// 头像Box(modifier = Modifier.size(60.dp).background(Color.Gray, CircleShape),contentAlignment = Alignment.Center) {Text(text = user.name.first().toString(),color = Color.White,fontSize = 24.sp)}Spacer(modifier = Modifier.width(16.dp))// 用户信息Column(modifier = Modifier.weight(1f)) {Text(text = user.name,fontSize = 18.sp,fontWeight = FontWeight.Bold)Text(text = user.email,fontSize = 14.sp,color = Color.Gray)}// 箭头图标Icon(imageVector = Icons.Default.ArrowForward,contentDescription = "查看详情")}}
}// 使用方式
@Composable
fun UserList() {val users = listOf(User(1, "张三", "zhangsan@example.com"),User(2, "李四", "lisi@example.com"),User(3, "王五", "wangwu@example.com"))LazyColumn {items(users) { user ->UserCard(user = user,onUserClick = { selectedUser ->// 处理用户点击println("点击了用户: ${selectedUser.name}")})}}
}
组件间通信
回调函数
通过回调函数实现父子组件通信:
@Composable
fun TodoItem(todo: String,isCompleted: Boolean,onToggle: () -> Unit,onDelete: () -> Unit
) {Row(modifier = Modifier.fillMaxWidth().padding(8.dp),verticalAlignment = Alignment.CenterVertically) {Checkbox(checked = isCompleted,onCheckedChange = { onToggle() })Text(text = todo,modifier = Modifier.weight(1f).padding(horizontal = 8.dp),textDecoration = if (isCompleted) TextDecoration.LineThrough else null)IconButton(onClick = onDelete) {Icon(Icons.Default.Delete, contentDescription = "删除")}}
}@Composable
fun TodoList() {var todos by remember {mutableStateOf(listOf("学习 Compose" to true,"写博客文章" to false,"做运动" to false))}LazyColumn {itemsIndexed(todos) { index, (todo, isCompleted) ->TodoItem(todo = todo,isCompleted = isCompleted,onToggle = {todos = todos.mapIndexed { i, item ->if (i == index) item.copy(second = !item.second)else item}},onDelete = {todos = todos.filterIndexed { i, _ -> i != index }})}}
}
ViewModel 共享状态
对于复杂的状态管理,可以使用 ViewModel:
class TodoViewModel : ViewModel() {private val _todos = mutableStateListOf<Pair<String, Boolean>>()val todos: List<Pair<String, Boolean>> = _todosfun addTodo(todo: String) {_todos.add(todo to false)}fun toggleTodo(index: Int) {_todos[index] = _todos[index].copy(second = !_todos[index].second)}fun deleteTodo(index: Int) {_todos.removeAt(index)}
}@Composable
fun TodoApp() {val viewModel = viewModel<TodoViewModel>()var newTodo by remember { mutableStateOf("") }Column {TextField(value = newTodo,onValueChange = { newTodo = it },label = { Text("添加新任务") },modifier = Modifier.fillMaxWidth().padding(16.dp))Button(onClick = {if (newTodo.isNotBlank()) {viewModel.addTodo(newTodo)newTodo = ""}},modifier = Modifier.padding(horizontal = 16.dp)) {Text("添加")}LazyColumn {itemsIndexed(viewModel.todos) { index, (todo, isCompleted) ->TodoItem(todo = todo,isCompleted = isCompleted,onToggle = { viewModel.toggleTodo(index) },onDelete = { viewModel.deleteTodo(index) })}}}
}
最佳实践
1. 组件命名规范
// ✅ 好的命名
@Composable
fun UserProfileCard() { }@Composable
fun LoadingIndicator() { }// ❌ 不好的命名
@Composable
fun Card() { } // 与现有组件冲突@Composable
fun Component1() { } // 命名不清晰
2. 参数默认值
@Composable
fun MyComponent(title: String,subtitle: String? = null,icon: ImageVector? = null,onClick: (() -> Unit)? = null,modifier: Modifier = Modifier
) {// 实现
}
3. 使用 Modifier 作为最后一个参数
@Composable
fun CustomCard(title: String,content: String,modifier: Modifier = Modifier // 放在最后
) {Card(modifier = modifier) {Column {Text(title)Text(content)}}
}
4. 合理拆分组件
@Composable
fun ProductCard(product: Product) {Card {Column {ProductImage(product.image)ProductInfo(name = product.name,price = product.price,description = product.description)ProductActions(onAddToCart = { /* */ },onFavorite = { /* */ })}}
}@Composable
private fun ProductImage(imageUrl: String) {AsyncImage(model = imageUrl,contentDescription = null,modifier = Modifier.fillMaxWidth())
}@Composable
private fun ProductInfo(name: String,price: String,description: String
) {Column(modifier = Modifier.padding(16.dp)) {Text(text = name,style = MaterialTheme.typography.headlineSmall)Text(text = price,style = MaterialTheme.typography.bodyLarge,color = MaterialTheme.colorScheme.primary)Text(text = description,style = MaterialTheme.typography.bodyMedium)}
}
5. 性能优化
// 使用 remember 优化计算
@Composable
fun ExpensiveComponent(data: List<String>) {val processedData by remember(data) {mutableStateOf(processData(data))}LazyColumn {items(processedData) { item ->Text(item)}}
}// 避免在 Composable 中创建大对象
@Composable
fun MyComponent() {val formatter = remember { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) }// 使用 formatter
}
总结
自定义组件是 Compose 开发的核心技能之一。通过合理的设计和实现,我们可以创建:
- 可重用的 UI 组件
- 易维护的代码结构
- 高性能的用户界面
- 一致的设计语言
关键要点:
- 状态提升:将状态提升到合适的层级
- 参数设计:提供合理的默认值和扩展性
- 组件拆分:保持组件的单一职责
- 性能考虑:合理使用 remember 和 derivedStateOf
- 命名规范:清晰、一致的命名约定
掌握这些技巧后,你就能创建出高质量、可维护的 Compose 应用程序了。