Jetpack Compose初体验

入门学习

由于工作需要,我们当前要在老代码的基础上使用 Compose 进行新页面的开发,这项工作主要落在我的身上。因此,我需要先了解 Compose。

这里我入门看的是写给初学者的Jetpack Compose教程,Lazy Layout,有兴趣可以看看。

了解 Compose

Compose 是 Google 推出的用于构建原生界面的现代工具包,专门用于 Android 平台的声明式 UI 开发。基于 Kotlin 语言,Compose 利用 Kotlin 的特性来简化 UI 开发过程,核心思想是使用函数来描述 UI 的外观和行为。

Compose 与传统 Android 开发的区别

1. 编程范式
  • 传统 Android 开发:采用命令式编程范式,开发者需要手动管理 UI 组件的创建、更新和销毁。
  • Compose 开发:采用声明式编程范式,开发者只需描述 UI 的最终状态,Compose 会自动更新 UI。
2. 布局系统
  • 传统 Android 开发:使用 XML 布局文件和 Java/Kotlin 代码分离的方式。
  • Compose 开发:使用 Kotlin 代码直接构建布局,布局代码和逻辑代码可以放在一起。
3. 状态管理
  • 传统 Android 开发:手动处理状态的保存和恢复。
  • Compose 开发 :内置状态管理机制,使用 mutableStateOf,状态变化会自动更新 UI。
4. 开发效率
  • 传统 Android 开发:代码量较大,开发效率相对较低。
  • Compose 开发:代码简洁,声明式编程和内置状态管理机制提高了开发效率。

初试 Compose

从 AI 的搜索结果来看,Compose 开发具有以下特点:声明式编程、基于 Kotlin、高效的状态管理、简洁的布局系统、实时预览、与现有代码兼容。

环境搭建

想要进行 Compose 开发,首先要引入 Compose 环境。Android Studio 新版可以直接创建 Compose 项目,但我当前的版本暂未提供这一功能。可以通过在 build.gradle 文件中添加相关依赖来引入 Compose。

公共基类

我创建了一个 BaseComposeActivity 作为 Compose 页面的公共基类,它继承自 AppCompatActivity,并在 onCreate 方法中使用 setContent 来设置 Compose UI。

基本布局

根据产品的原型图,我基于平板和手机做出了不同的响应式布局。只需通过 @Composable 函数来组合不同的组件,即可实现 Activity 显示不同的信息。

主页面逻辑

我注册了一个主页面 DevicePolicyActivity,在其中定义了基本布局并对平板和手机进行了区分。通过 TopBarRow 组件来安排导航栏和内容区域,使用 navigationNumberhideSelectTac 来控制不同的页面显示。

响应式布局
  • 平板端工作台:显示两列布局,左侧是目录,右侧是内容详情。
  • 非平板端工作台:基于页面跳转来显示详情页面。
策略目录菜单

目录菜单通过 DirectoryItemStrategyItem 组件来实现。通过点击菜单项更新选中状态,并相应地改变页面显示。对于非平板逻辑页面,点击后触发页面跳转,通过 forceRecompose 来手动触发页面重组。

DP根据逻辑生成的

这是ai根据我描述逻辑生成的,基本逻辑是一样的,但状态管理我都是基于remember和mutableIntStateOf进行管理的,其他意思到了就行:

代码实现细节

以下是基于 Compose 实现的核心代码逻辑和关键设计思路,结合具体场景进行说明:


1. BaseComposeActivity 基类
kotlin 复制代码
abstract class BaseComposeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyAppTheme {  // 自定义主题配置
                AppContent() // 主 UI 入口
            }
        }
    }

    @Composable
    abstract fun AppContent()
}

设计要点:

  • 统一 Activity 入口模板
  • 强制子类实现 AppContent 方法作为 UI 入口
  • 集成 Material Design 主题系统

2. 主页面 DevicePolicyActivity 实现
kotlin 复制代码
class DevicePolicyActivity : BaseComposeActivity() {
    private val viewModel: PolicyViewModel by viewModels()
    
    @Composable
    override fun AppContent() {
        val isTablet = LocalConfiguration.current.screenWidthDp >= 600
        
        if (isTablet) {
            TabletLayout(viewModel)
        } else {
            PhoneLayout(viewModel)
        }
    }
}

设备检测逻辑:

kotlin 复制代码
@Composable
fun isTabletMode(): Boolean {
    val configuration = LocalConfiguration.current
    return configuration.screenWidthDp >= 600 || 
           configuration.smallestScreenWidthDp >= 600
}

3. 平板双栏布局实现
kotlin 复制代码
@Composable
private fun TabletLayout(viewModel: PolicyViewModel) {
    Scaffold(
        topBar = { AppTopBar(viewModel.selectedTitle) }
    ) { padding ->
        Row(Modifier.padding(padding)) {
            // 左侧目录(占30%宽度)
            NavigationColumn(
                Modifier.weight(0.3f),
                viewModel.policies,
                viewModel::selectPolicy
            )
            
            // 右侧详情(占70%宽度)
            PolicyDetail(
                Modifier.weight(0.7f),
                viewModel.selectedPolicy
            )
        }
    }
}

关键交互逻辑:

kotlin 复制代码
// 状态管理示例
class PolicyViewModel : ViewModel() {
    private val _selectedPolicy = mutableStateOf<Policy?>(null)
    val selectedPolicy: State<Policy?> = _selectedPolicy
    
    fun selectPolicy(policy: Policy) {
        _selectedPolicy.value = policy
    }
}

4. 手机端列表-详情跳转
kotlin 复制代码
@Composable
private fun PhoneLayout(viewModel: PolicyViewModel) {
    val navController = rememberNavController()
    
    NavHost(navController, startDestination = "policyList") {
        composable("policyList") {
            PolicyListScreen(viewModel) { policy ->
                navController.navigate("policyDetail/${policy.id}")
            }
        }
        composable("policyDetail/{policyId}") { backStackEntry ->
            PolicyDetailScreen(
                policyId = backStackEntry.arguments?.getString("policyId"),
                onBack = { navController.popBackStack() }
            )
        }
    }
}

5. 策略目录组件实现
kotlin 复制代码
@Composable
fun NavigationColumn(
    modifier: Modifier = Modifier,
    policies: List<Policy>,
    onSelect: (Policy) -> Unit
) {
    LazyColumn(modifier) {
        items(policies) { policy ->
            DirectoryItem(
                policy = policy,
                isSelected = policy == viewModel.selectedPolicy,
                onClick = { onSelect(policy) }
            )
        }
    }
}

@Composable
private fun DirectoryItem(
    policy: Policy,
    isSelected: Boolean,
    onClick: () -> Unit
) {
    Surface(
        color = if (isSelected) MaterialTheme.colors.primary.copy(alpha = 0.1f) 
               else Color.Transparent,
        modifier = Modifier
            .fillMaxWidth()
            .clickable { onClick() }
    ) {
        Text(
            text = policy.title,
            modifier = Modifier.padding(16.dp),
            style = MaterialTheme.typography.subtitle1
        )
    }
}

6. 强制重组逻辑优化
kotlin 复制代码
// 使用 remember 配合 key 控制重组
var recomposeKey by remember { mutableStateOf(0) }

LaunchedEffect(Unit) {
    // 当需要强制重组时
    recomposeKey++
}

Column(modifier = Modifier.remember(recomposeKey) { /*...*/ }) {
    // 动态内容
}

替代方案:

kotlin 复制代码
// 使用 derivedStateOf 精细化控制
val filteredPolicies = remember(policies, searchQuery) {
    derivedStateOf {
        policies.filter { it.contains(searchQuery) }
    }
}

遇到的典型问题与解决方案

问题 1:状态更新未触发重组

现象: 修改 mutableStateOf 的值后 UI 未更新
解决方案:

kotlin 复制代码
// 错误写法
var count = mutableStateOf(0)
Button(onClick = { count.value++ }) 

// 正确写法
val count = remember { mutableStateOf(0) }
Button(onClick = { count.value++ })
问题 2:列表性能问题

现象: LazyColumn 滚动卡顿
优化方案:

kotlin 复制代码
items(policies, key = { it.id }) { policy ->
    // 使用稳定唯一的 key
}
问题 3:主题配置冲突

解决方案: 创建独立主题配置

kotlin 复制代码
@Composable
fun MyAppTheme(content: @Composable () -> Unit) {
    MaterialTheme(
        colors = darkColors(
            primary = Color(0xFFBB86FC),
            secondary = Color(0xFF03DAC6)
        ),
        typography = MyTypography,
        content = content
    )
}

相关推荐
遇见火星1 小时前
日常真实工作环境,Mysql常用操作命令,笔记!
android·mysql·adb·常用命令·mysql日志
刘龙超1 小时前
如何应对 Android 面试官 -> 网络如何优化?
android·java
iReachers1 小时前
PDF转安卓APP软件, 支持加密添加一机一码, 静态密码, 保护APK版权使用说明和CSDN文库下载
android·pdf·pdf加密·pdf转app·pdf转apk·一机一码加密
stevenzqzq1 小时前
kotlin中主构造函数是什么
开发语言·python·kotlin
tangweiguo030519872 小时前
(Kotlin) Android使用DialogX实现iOS风格底部弹窗(带Toggle开关)
android·kotlin
wangz762 小时前
kotlin,Jetpack Compose使用Scaffold布局,包含底部导航栏
android·kotlin·jetpack compose·navigationbar
鸿蒙布道师2 小时前
鸿蒙NEXT开发日期工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
pengyu3 小时前
系统化掌握Dart网络编程之Dio(四):拦截器篇
android·flutter·dart
行墨3 小时前
Kotlin延时加载
android