目前使用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混合使用
- 在xml中引入Compose
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
- 在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")
}
}
}
// ...
}
- 设置预览
@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没有设置外边距的方式,只能设置内边距以及内部控件对齐方式
- 设置内部控件对齐方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
-
remember可以起到保护 作用,防止状态在重组时被重置。 -
remember函数仅在可组合项包含在组合中时起作用。旋转屏幕后,整个 activity 都会重启,所有状态都将丢失。当发生任何配置更改或者进程终止时,也会出现这种情况。
可以使用rememberSaveable,而不使用remember。这会保存每个在配置更改(如旋转)和进程终止后保留下来的状态。
var expanded by rememberSaveable { mutableStateOf(false) }
创建高效延迟列表
使用 LazyColumn。LazyColumn 只会渲染屏幕上可见的内容,从而在渲染大型列表时提升效率。
注意: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)。