Android 开发 Compose 的使用

基础入门

环境配置与依赖

在 Android 项目中集成 Jetpack Compose 需要:

  1. Android Studio 北极狐(Arctic Fox)或更高版本
  2. Gradle 7.0+
  3. 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 :使用 Logprintln 调试
  • 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 缓存计算和对象
  • 拆分大型组件为小型组件
  • 使用 shouldUpdatederivedStateOf 控制重组

状态管理复杂

  • 考虑使用状态容器模式
  • 对于复杂应用,考虑使用 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()
}
相关推荐
爷_3 小时前
字节跳动震撼开源Coze平台!手把手教你本地搭建AI智能体开发环境
前端·人工智能·后端
charlee445 小时前
行业思考:不是前端不行,是只会前端不行
前端·ai
Amodoro5 小时前
nuxt更改页面渲染的html,去除自定义属性、
前端·html·nuxt3·nuxt2·nuxtjs
Wcowin6 小时前
Mkdocs相关插件推荐(原创+合作)
前端·mkdocs
伍哥的传说6 小时前
CSS+JavaScript 禁用浏览器复制功能的几种方法
前端·javascript·css·vue.js·vue·css3·禁用浏览器复制
lichenyang4536 小时前
Axios封装以及添加拦截器
前端·javascript·react.js·typescript
Trust yourself2437 小时前
想把一个easyui的表格<th>改成下拉怎么做
前端·深度学习·easyui
三口吃掉你7 小时前
Web服务器(Tomcat、项目部署)
服务器·前端·tomcat
Trust yourself2437 小时前
在easyui中如何设置自带的弹窗,有输入框
前端·javascript·easyui
烛阴7 小时前
Tile Pattern
前端·webgl