安卓学习笔记-声明式UI

06-01 1690阅读

声明式UI

Jetpack Compose 是 Google 推出的用于构建 Android UI 的现代化工具包。它采用 声明式编程模型(Declarative UI),用 Kotlin 编写,用于替代传统的 XML + View 的方式。一句话概括:Jetpack Compose = 用 Kotlin 代码写 UI,简洁、响应式、可组合

一个小例子对比

传统 XML + View 写法:

安卓学习笔记-声明式UI

// MainActivity.kt
val textView = findViewById(R.id.helloText)
textView.text = "Hello Compose"

Jetpack Compose 写法:

@Composable
fun Greeting() {
    Text("Hello Compose")
}

实现一个页面

ComponentActivity 是 Android Jetpack 架构组件中的一个核心类,它是 Activity 的子类,为现代 Android 应用程序开发提供了额外的功能。

下面是一个 简洁清晰的 Jetpack Compose 风格 Activity 开发流程说明,帮助你快速掌握使用 Jetpack Compose 编写页面的基本步骤。

使用 Jetpack Compose 编写一个 Activity 的标准流程

第 1 步:创建一个继承 ComponentActivity 的类

class MainActivity : ComponentActivity() {
    ...
}

ComponentActivity 是 Jetpack Compose 所需的基础 Activity 类型。比通常用于 XML + View 系统的传统 UI 开发的AppCompatActivity 更轻量,适合纯 Compose 页面。

第 2 步:重写 onCreate() 方法并调用 setContent {}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        // 调用你的 Compose UI 入口函数
        MyApp()
    }
}

setContent {} 是 Compose 的入口,用于设置 UI。

括号内是一个或多个 @Composable 函数。

第 3 步:编写你的 @Composable 函数,组织 UI

@Composable
fun MyApp() {
    Text("Hello Jetpack Compose")
}

使用 @Composable 注解定义可组合的 UI 函数。

可组合函数可嵌套、组合、响应状态。

最简版 Activity 示例:

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp()
        }
    }
}
@Composable
fun MyApp() {
    Text("你好,Compose!")
}

总结一句话流程:

创建 ComponentActivity → 重写 onCreate() → 调用 setContent {} → 写一个或多个 @Composable 函数来构建 UI。

编写一个预览函数(Preview)

当然,Jetpack Compose 提供了非常方便的 UI 预览功能,你可以不用运行 App,就能在 Android Studio 中看到某个 @Composable 函数的界面效果。

你只需要:

  1. 给你的 @Composable 函数加一个单独的预览函数
  2. 用 @Preview 注解标记它
  3. 这个函数内部调用你想预览的 UI

示例:为 MyApp() 编写一个预览函数

import androidx.compose.ui.tooling.preview.Preview
@Preview(showBackground = true, name = "默认预览")
@Composable
fun MyAppPreview() {
    MyApp()
}

注解参数说明

  • showBackground = true 显示一个浅色背景,便于在 IDE 中观察 UI 布局
  • name = "..." 给这个预览命名,方便识别多个预览函
  • widthDp / heightDp 可选,用于指定预览尺
  • uiMode 可指定暗色模式、横屏模式等(如 Configuration.UI_MODE_NIGHT_YES)

    如何看预览的UI

    1. 写好带 @Preview 的函数
    2. 在 Android Studio 中打开这个文件
    3. 点击 “Split” / “Design” 预览按钮即可查看 UI 渲染效果

      安卓学习笔记-声明式UI

    完整代码

    import android.os.Bundle
    import androidx.activity.ComponentActivity
    import androidx.activity.compose.setContent
    import androidx.compose.material3.*
    import androidx.compose.runtime.Composable
    import androidx.compose.ui.tooling.preview.Preview
    class DemoActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                MyApp()
            }
        }
    }
    @Composable
    fun MyApp() {
        Text("你好,Compose!")
    }
    @Preview(showBackground = true, name = "默认预览")
    @Composable
    fun MyAppPreview() {
        MyApp()
    }
    

    Jetpack Compose 的响应式

    Jetpack Compose 最大的特点之一,就是它采用了 响应式编程模型(reactive programming model) 来构建 UI。

    什么是“响应式”的方式构建 UI?

    简单来说:界面不再是你手动更新的,而是“自动响应数据变化”的。

    对比传统 View 编程(命令式):

    // 传统 View 写法(命令式)
    textView.text = "你好," + name  // 你得手动更新 UI
    

    你修改了 name,你得自己调用 textView.setText(...) 去更新。

    Jetpack Compose 的响应式写法:

    @Composable
    fun Greeting(name: String) {
        Text("你好,$name!")  // name 变了,Text 自动刷新
    }
    

    你只要修改 name 的状态,UI 会自动重新组合并刷新显示,你不再需要手动更新 UI 控件。

    关键机制:remember 和 mutableStateOf

    这是 Compose 中响应式状态管理的核心工具:

    var name by remember { mutableStateOf("Compose") }
    

    这行代码的作用是:

    创建一个名为 name 的状态变量,这个变量的初始值是字符串 “Compose”

    当你在 UI 中使用这个变量时,只要它发生变化,相关 UI 会自动刷新(响应式)

    mutableStateOf

    mutableStateOf创建一个可以被 Compose 跟踪的可变状态值.一旦值改变,使用它的 Compose 组件会自动 recompose(重新绘制).

    mutableStateOf 是一个泛型函数,可以接受任何类型 T. 例如:基础类型、自定义数据类 (Data classes)、集合类型(List, Set, Map)、任何其他 Kotlin 类实例.

    示例

    • 集合
      val itemList = mutableStateOf(listOf("Apple", "Banana"))
      val userMap = mutableStateOf(mapOf("id1" to "Alice", "id2" to "Bob"))
      

      注意: 对于集合类型(List, Set, Map),mutableStateOf 包装的是集合本身。这意味着如果你只是修改集合的内容(例如 itemList.value.add(“Orange”)),Compose 不会自动检测到这种内部变化并触发重组。要触发重组,你需要赋一个新的集合实例:

      • 类实例
        class MyCustomObject(var data: String)
        val myObject = mutableStateOf(MyCustomObject("Initial Data"))
        

        remember

        remember在重组(Recomposition)时记住状态值,避免它被重置

        如果你写:

        val counter = mutableStateOf(0)
        

        这个状态会在每次重组时被重新创建为 0,导致 UI 刷新后又变回初始值。所以你要:

        val counter = remember { mutableStateOf(0) }
        

        这样 Compose 就会“记住”这个状态,不在每次重组时重新创建它。

        Compose 自动响应过程示意图

        状态 (mutableStateOf) --> 改变 → Compose 监听到 → 重组(重绘 UI)
        

        示例:响应式 UI 的体现

        package com.wy.diary.activity
        import android.os.Bundle
        import androidx.activity.ComponentActivity
        import androidx.activity.compose.setContent
        import androidx.compose.foundation.layout.Column
        import androidx.compose.foundation.layout.Spacer
        import androidx.compose.foundation.layout.height
        import androidx.compose.foundation.layout.padding
        import androidx.compose.material3.*
        import androidx.compose.runtime.Composable
        import androidx.compose.runtime.getValue
        import androidx.compose.runtime.mutableStateOf
        import androidx.compose.runtime.remember
        import androidx.compose.runtime.setValue
        import androidx.compose.ui.Modifier
        import androidx.compose.ui.tooling.preview.Preview
        import androidx.compose.ui.unit.dp
        class DemoActivity : ComponentActivity() {
            override fun onCreate(savedInstanceState: Bundle?) {
                super.onCreate(savedInstanceState)
                setContent {
                    MyApp()
                }
            }
        }
        @Composable
        fun MyApp() {
            var name by remember { mutableStateOf("Compose") }
            Column(modifier = Modifier.padding(16.dp)) {
                TextField(
                    value = name,
                    onValueChange = { name = it },  // 用户输入改变 name
                    label = { Text("输入名字") }
                )
                Spacer(modifier = Modifier.height(16.dp))
                Text("你好,$name!") // name 变了,这里自动更新
            }
        }
        @Preview(showBackground = true, name = "默认预览")
        @Composable
        fun MyAppPreview() {
            MyApp()
        }
        

        安卓学习笔记-声明式UI

        代码分层(UI、ViewModel)

        在 Compose 中,虽然你可以用 remember { mutableStateOf(...) } 存储 UI 状态,但这种状态只在 Composable 内部存在,页面重建(如屏幕旋转)就会丢失。

        为什么会丢失状态?

        这里的“页面重建”如果指的是配置变更(Configuration Changes),例如屏幕旋转,那么状态会丢失的原因在于:

        当 Android 设备发生配置变更时(如屏幕从竖屏旋转到横屏):

        1. Activity/Fragment 会被销毁并重建。 这是 Android 系统为了适应新的配置(例如,加载新的布局资源、调整屏幕尺寸等)的默认行为。
        2. Activity 的生命周期方法会被重新调用。 包括 onCreate()、onStart()、onResume() 等。
        3. 您的 @Composable 函数所在的整个 Composable 树也会被销毁并从头开始构建。
        4. 当 Composable 重新执行时,remember { ... } 块内的代码会重新执行。由于它是一个新的执行上下文,mutableStateOf(0) 会重新创建一个新的 MutableState 实例,并用其初始值 0 覆盖之前的值。

        简而言之,remember 只在当前 Composable 实例的生命周期**内保持状态。一旦 Composable 实例因为其宿主(如 Activity)被销毁而重建,remember 的作用范围就结束了,所有用 remember 记住的状态都会被重置为它们的初始值。

        示例:

        如果你的 counter 设为 remember { mutableStateOf(0) },用户点击了几次按钮,counter 变成了 5。然后用户旋转屏幕,Activity 重建,MyApp Composable 重新执行,remember { mutableStateOf(0) } 再次被调用,counter 会被重新初始化为 0,之前的 5 就丢失了。

        哪些“重建”会丢失状态?

        会丢失使用 remember { mutableStateOf(...) } 存储状态的常见的配置变更包括:

        • 屏幕旋转(横竖屏切换)
        • 键盘可用性变化(软键盘弹出/隐藏)
        • 语言区域设置变化
        • 显示大小、字体大小变化
        • 主题切换

          此外,还有一些非配置变更但也会导致 Activity 销毁重建的情况:

        • 系统回收进程: 当系统内存不足时,可能会回收后台 Activity 所在的进程。
        • 用户从最近任务列表中滑动关闭应用
        • Activity 被显式地 finish()

          如何在配置变更时保留 UI 状态?

          为了在配置变更时保留 UI 状态,可以使用ViewModel。

          ViewModel 是一个专门用来存储和管理界面相关数据的类,它与界面组件(如 Activity 或 Composable)生命周期隔离,因此可以安全地保存状态,即使界面配置发生变化(如屏幕旋转)。

          Activity 创建
          └── ViewModel 创建(只会创建一次)
          Activity 销毁重建(如横竖屏切换)
          └── ViewModel 保持不变,状态仍在
          Activity 被真正销毁(退出)
          └── ViewModel 被清理
          

          接下来我会将前面的例子改造成,使用 ViewModel + StateFlow + Compose UI 的最佳实践写法。

          改造思路

          我们需要:

          1. 创建一个 ViewModel 管理状态
          2. 用 StateFlow 来代替 remember 管理的状态
          3. 在 Composable 中使用 viewModel() 获取 ViewModel,并通过 collectAsState() 观察状态

          第一步:创建 ViewModel

          创建一个类 DemoViewModel.kt:

          package com.wy.diary.viewmodel
          import androidx.lifecycle.ViewModel
          import kotlinx.coroutines.flow.MutableStateFlow
          import kotlinx.coroutines.flow.StateFlow
          class DemoViewModel : ViewModel() {
              private val _name = MutableStateFlow("Compose")   // 私有可变状态
              val name: StateFlow = _name               // 对外只读
              fun updateName(newName: String) {
                  _name.value = newName
              }
          }
          

          第二步:修改 Composable 来使用 ViewModel

          修改 MyApp() 方法:

          @Composable
          fun MyApp(viewModel: DemoViewModel = viewModel()) {
              val name by viewModel.name.collectAsState()
              Column(modifier = Modifier.padding(16.dp)) {
                  TextField(
                      value = name,
                      onValueChange = { viewModel.updateName(it) },  // 通知 ViewModel 更新状态
                      label = { Text("输入名字") }
                  )
                  Spacer(modifier = Modifier.height(16.dp))
                  Text("你好,$name!")  // 自动根据 StateFlow 更新
              }
          }
          

          这里有两个地方需要说明一下

          • viewModel

            Compose 提供的 viewModel() 函数 来获取具体的ViewModel类,需要引入依赖,下面是Kotlin DSL写法

            dependencies {
                // ....省略其他依赖
                implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
            }
            
            • collectAdState

              这里 collectAsState() 把 StateFlow(响应式流) 转换成 Compose 可观察的 State 对象,这样 UI 就能根据变化自动重组。

              第三步:预览函数改动

              因为引入了 viewModel(),它只能在运行时由 Composition Local 提供的 ViewModelStoreOwner 支持,而 Preview 模式下并不具备这个环境,所以我们不能在 @Preview 中直接使用包含 viewModel() 的 Composable 函数。

              解决思路是:将 UI 部分抽离出来,变成一个纯函数式的 Composable,接收状态和回调作为参数,这样就可以在预览时传入假的数据。

              代码再次调整我们将 MyApp() 拆成两层

              展示 UI 的纯 Composable

              @Composable
              fun MyAppContent(name: String, onNameChange: (String) -> Unit) {
                  Column(modifier = Modifier.padding(16.dp)) {
                      TextField(
                          value = name,
                          onValueChange = onNameChange,
                          label = { Text("输入名字") }
                      )
                      Spacer(modifier = Modifier.height(16.dp))
                      Text("你好,$name!")
                  }
              }
              

              包装 ViewModel 的 MyApp()

              @Composable
              fun MyApp(viewModel: DemoViewModel = viewModel()) {
                  val name by viewModel.name.collectAsState()
                  MyAppContent(name = name, onNameChange = { viewModel.updateName(it) })
              }
              

              添加 Preview

              现在我们可以优雅地添加预览函数:

              @Preview(showBackground = true)
              @Composable
              fun MyAppPreview() {
                  MyAppContent(name = "预览模式", onNameChange = {})
              }
              

              通过这种拆分方式,我们既保留了使用 ViewModel 的响应式结构,也让 UI 层具备了良好的可测试性和可预览性。

              完整代码

              这是完整的改造后代码,包括 ViewModel、Composable UI、Preview 和依赖说明

              添加依赖(Kotlin DSL)

              在 build.gradle.kts 中加入:

              dependencies {
                  implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
              }
              

              DemoViewModel.kt

              package com.wy.diary.viewmodel
              import androidx.lifecycle.ViewModel
              import kotlinx.coroutines.flow.MutableStateFlow
              import kotlinx.coroutines.flow.StateFlow
              class DemoViewModel : ViewModel() {
                  private val _name = MutableStateFlow("Compose")
                  val name: StateFlow = _name
                  fun updateName(newName: String) {
                      _name.value = newName
                  }
              }
              

              DemoActivity.kt

              package com.wy.diary
              import android.os.Bundle
              import androidx.activity.ComponentActivity
              import androidx.activity.compose.setContent
              import androidx.activity.viewModels
              import androidx.compose.material.MaterialTheme
              import androidx.compose.material.Surface
              import com.wy.diary.ui.MyApp
              import com.wy.diary.viewmodel.DemoViewModel
              class DemoActivity : ComponentActivity() {
                  private val viewModel: DemoViewModel by viewModels()
                  override fun onCreate(savedInstanceState: Bundle?) {
                      super.onCreate(savedInstanceState)
                      setContent {
                          MaterialTheme {
                              Surface {
                                  MyApp(viewModel)
                              }
                          }
                      }
                  }
              }
              

              MyApp.kt(UI 和 Preview)

              package com.wy.diary.ui
              import androidx.compose.foundation.layout.*
              import androidx.compose.material.*
              import androidx.compose.runtime.*
              import androidx.compose.ui.Modifier
              import androidx.compose.ui.tooling.preview.Preview
              import androidx.compose.ui.unit.dp
              import androidx.lifecycle.viewmodel.compose.viewModel
              import com.wy.diary.viewmodel.DemoViewModel
              @Composable
              fun MyApp(viewModel: DemoViewModel = viewModel()) {
                  val name by viewModel.name.collectAsState()
                  MyAppContent(name = name, onNameChange = { viewModel.updateName(it) })
              }
              @Composable
              fun MyAppContent(name: String, onNameChange: (String) -> Unit) {
                  Column(modifier = Modifier.padding(16.dp)) {
                      TextField(
                          value = name,
                          onValueChange = onNameChange,
                          label = { Text("输入名字") }
                      )
                      Spacer(modifier = Modifier.height(16.dp))
                      Text("你好,$name!")
                  }
              }
              @Preview(showBackground = true)
              @Composable
              fun MyAppPreview() {
                  MyAppContent(name = "预览模式", onNameChange = {})
              }
              

              最终文件结构

              安卓学习笔记-声明式UI

              ui.theme是创建empty项目时候就有了的,不必理会

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

目录[+]

取消
微信二维码
微信二维码
支付宝二维码