Jetpack Compose的学习记录

目前使用xml和Compose混合构建UI

使用Compose的方式

一、 整个页面使用Compose
将Activity继承自ComponentActivity,并将setContentView或binding换成setContent

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            GTAStartTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = "Android",
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }

二、 xml和compose混合使用

  1. 在xml中引入Compose
<androidx.compose.ui.platform.ComposeView
                android:id="@+id/compose_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>
  1. 在kt里设置Compose界面
// 在初始化UI后
// ...
override fun onCreateView(...): View? {
    val binding = DataBindingUtil.inflate<FragmentPlantDetailBinding>(
        inflater, R.layout.fragment_plant_detail, container, false
    ).apply {
        // ...
        composeView.setContent {
            // You're in Compose world!
            MaterialTheme {
                PlantName("Apple")
              }
              }
        }
    // ...
}
  1. 设置预览
@Preview(
    name = "植物名称预览", // 给预览一个名字
    showBackground = true, // 是否显示预览的背景颜色
    backgroundColor = 0xFFFFFF, // 当showBackground为true时生效
    widthDp = 200, // 宽度
    heightDp = 400, // 高度
    uiMode = Configuration.UI_MODE_NIGHT_NO, // 指定ui模式,如UI_MODE_NIGHT_NO | UI_MODE_NIGHT_YES
    locale = "en", // 预览时的本地化语言
    fontScale = 1.0f // 字体的缩放比例
)
@Composable
fun PlantNamePreview() {
    MaterialTheme {
         PlantName("Apple")
    }
}

Modifier 修饰符

设置权重,android:layout_weight

Modifier.weight(1f)

Compose没有设置外边距的方式,只能设置内边距以及内部控件对齐方式

  1. 设置内部控件对齐方android:layout_gravity=“center_horizontal”

Modifier.wrapContentWidth(Alignment.CenterHorizontally)

Compose中的状态

若想向可组合项添加内部状态,可以使用 mutableStateOf 函数,该函数可让 Compose 重组读取状态,类似于viewModel中的 LiveData(只读)MutableLiveData
然后使用 remember 记住可变状态

@Composable
fun Greeting(...) {
    /*
    val expanded = remember { mutableStateOf(false) } // 记住控件是否展开
    val extraPadding = if (expanded.value) 48.dp else 0.dp
    */

    // 也可以使用 by 关键字,而不是 =。这是一个属性委托,可以无需每次都输入 .value。
    val expanded by remember { mutableStateOf(false) }
    val extraPadding = if (expanded) 48.dp else 0.dp

    ElevatedButton(
        onClick = { expanded = !expanded },
    ) {
          Text(if (expanded) "Show less" else "Show more")
      }
    }

关于 remember

  1. remember 可以起到保护 作用,防止状态在重组时被重置。

  2. remember 函数仅在可组合项包含在组合中时起作用。旋转屏幕后,整个 activity 都会重启,所有状态都将丢失。当发生任何配置更改或者进程终止时,也会出现这种情况。
    可以使用 rememberSaveable,而不使用 remember。这会保存每个在配置更改(如旋转)和进程终止后保留下来的状态。

   var expanded by rememberSaveable { mutableStateOf(false) }

创建高效延迟列表

使用 LazyColumnLazyColumn 只会渲染屏幕上可见的内容,从而在渲染大型列表时提升效率。

注意:LazyColumn 和 LazyRow 相当于 Android View 中的 RecyclerView。

LazyColumn API 会在其作用域内提供一个 items 元素,并在该元素中编写各项内容的渲染逻辑

@Composable
private fun Greetings(
    modifier: Modifier = Modifier,
    names: List<String> = List(1000) { "$it" }
) {
    LazyColumn(modifier = modifier.padding(vertical = 4.dp)) {
        items(items = names) { name ->
            Greeting(name = name)
        }
    }
}

动画效果

1. animateDpAsState

该可组合项会返回一个 State 对象,该对象的 value 会被动画持续更新,直到动画播放完毕。该可组合项需要一个类型为 Dp 的“目标值”

用人话来说就是设置“当布局发生变化的时候”的过渡动画,例如设置某控件不可见时,不会瞬间消失,而是使用自然动画过渡

var expanded by rememberSaveable { mutableStateOf(false) }
val extraPadding by animateDpAsState(
    if (expanded) 48.dp else 0.dp
)

Row(modifier = Modifier.padding(24.dp)) {
    Column(modifier = Modifier
        .weight(1f)
        .padding(bottom = extraPadding) // 设置底部内边距为带动画效果的状态
    ) {
        Text(text = "Hello, ")
        Text(text = name)
    }
    ElevatedButton(
        onClick = { expanded = !expanded } // 点击按钮时修改状态
    ) {
        Text(if (expanded) "Show less" else "Show more")
    }

}

animateDpAsState 接受可选的 animationSpec 参数自定义动画。比如添加基于弹簧的动画:

Column(modifier = Modifier
    .weight(1f)
    .padding(bottom = extraPadding.coerceAtLeast(0.dp) // 添加基于弹簧的动画
)

// 或者可以不使用extraPadding
// 移除extraPadding并改为将 animateContentSize 修饰符应用于 Row。
// 这会自动执行创建动画的过程,spring 动画能够提供自然的过渡效果
Row(
    modifier = Modifier
        .padding(12.dp)
        .animateContentSize(
            animationSpec = spring(
                // 阻尼比,控制动画振动的衰减速度,目前为中等弹性
                dampingRatio = Spring.DampingRatioMediumBouncy, 
                // 弹簧的刚度,影响动画的速度和弹性幅度,目前为较低的刚度,动画速度稍慢,弹性效果明显
                stiffness = Spring.StiffnessLow
            )
    )
)

使用资源

Compose 提供了从 dimens.xml 和 strings.xml 文件获取值的简单方法,即 dimensionResource(id) 和 stringResource(id)。