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
    )
}

相关推荐
千里马学框架20 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台20 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone21 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc21 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077001 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
ai2work1 天前
ch23 综合复刻:从零做一个最小可用版本(capstone)
kotlin
其实防守也摸鱼1 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
ai2work1 天前
ch21 签名、校验与发版
kotlin
AFinalStone1 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui