标准布局
修饰符
@Composable
fun PhotographerCard(modifier: Modifier = Modifier) {
Row(
modifier
.clip(RoundedCornerShape(4.dp))
.background(MaterialTheme.colorScheme.surface)
.clickable(onClick = {})
.padding(16.dp)
) {
Surface(
Modifier.size(50.dp),
shape = CircleShape,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f)
) {
Image(painter = painterResource(id = R.drawable.logo), contentDescription = null)
}
Column(modifier = Modifier.padding(start = 8.dp)) {
Text(text = "starry lixu", fontWeight = FontWeight.Bold)
Text(text = "3 minutes age", style = MaterialTheme.typography.bodyLarge)
}
}
}
槽位API
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LayoutStudy() {
Scaffold(
modifier = Modifier.background(MaterialTheme.colorScheme.primary),
topBar = {
TopAppBar(
title = {
Text(text = "top layout")
},
actions = {
IconButton(onClick = {}) {
Icon(imageVector = Icons.Filled.Favorite, contentDescription = null)
}
}
)
}
) { innerPadding ->
BodyContent(Modifier.padding(innerPadding))
}
}
@Composable
fun BodyContent(modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(8.dp)) {
Text(text = "hi there")
Text(text = "Thank for going through the LayoutStudy")
}
}
列表
@Composable
fun LazyList() {
val listSize = 100
val scrollState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
Column {
Row {
Button(
onClick = {
coroutineScope.launch {
scrollState.animateScrollToItem(0)
}
},
modifier = Modifier.weight(1f)
) {
Text(text = "top")
}
Button(
onClick = {
coroutineScope.launch {
scrollState.animateScrollToItem(listSize - 1)
}
},
modifier = Modifier.weight(1f)
) {
Text(text = "bottom")
}
}
LazyColumn(
state = scrollState
) {
items(100) {
LazyListItem(index = it)
}
}
}
}
@Composable
fun LazyListItem(index: Int) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
painter = painterResource(id = R.drawable.logo), contentDescription = null,
modifier = Modifier.size(50.dp)
)
Spacer(modifier = Modifier.size(10.dp))
Text(text = "Item #$index", style = MaterialTheme.typography.bodyLarge)
}
}
自定义布局
实现column的效果
@Composable
fun MyOwnColumn(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
//测量元素,每个子元素的placeables
val placeables = measurables.map { measurable ->
measurable.measure(constraints)
}
//累加高度
var yPosition = 0
//布局子元素的位置
layout(constraints.maxWidth, constraints.maxHeight) {
placeables.forEach { placeable ->
//设置子元素的位置
placeable.placeRelative(x = 0, y = yPosition)
yPosition += placeable.height
}
}
}
}
@Composable
fun MyOwnColumnSample(){
Compose_studyTheme {
MyOwnColumn {
Text(text = "你好")
Text(text = "你好")
Text(text = "你好")
Text(text = "你好")
Text(text = "你好")
}
}
}
自定义横向瀑布流
确定每一行的宽高-》 计算出布局的宽高-》 计算每一行元素的x,y坐标-》 同一行的元素y坐标是固定的,每一行的y坐标是前面所有行的高度-》 同一行中每一个元素的x坐标是同一行前面所有元素的宽度之和
val topics = listOf(
"hello world 1",
"hello world 22",
"hello world 333",
"hello world 4444",
"hello world 55555",
"hello world 666666",
"hello world 7777777",
"hello world 88888888",
"hello world 999999999",
"hello world XXXXXXXXXX",
"hello world YYYYYYYYYYY",
"hello world ZZZZZZZZZZZZ",
)
@Composable
fun StaggeredGrid(
modifier: Modifier = Modifier,
rows: Int = 3,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measureables, constraints ->
//用于保存每一行的宽度值
val rowWidths = IntArray(rows) { 0 }
//用于保存每一行的高度值
val rowHeights = IntArray(rows) { 0 }
val placeables = measureables.mapIndexed { index, measurable ->
//测量每一个元素
val placeable = measurable.measure(constraints)
val row = index % rows
//一行的宽度就是所有元素宽度之和
rowWidths[row] += placeable.width
//一行的高度就是最高的元素的高度
rowHeights[row] = max(rowHeights[row], placeable.height)
placeable
}
//计算整个布局的宽高
//宽度,所有行中最宽的那一行的宽度
val width = rowWidths.maxOrNull() ?: constraints.minWidth
//高度,所有行高度之和
val height = rowHeights.sumOf { it }
//设置每一行的y坐标
val rowY = IntArray(rows) { 0 }
for (i in 1 until rows) {
rowY[i] = rowY[i - 1] + rowHeights[i - 1]
}
layout(width, height) {
val rowX = IntArray(rows) { 0 }
placeables.forEachIndexed { index, placeable ->
//第一列,x坐标全部为0,下一列的x坐标要累加上前面元素的宽度
val row = index % rows
//设置当前元素的位置
placeable.placeRelative(x = rowX[row], y = rowY[row])
//计算下一列的x的坐标
rowX[row] += placeable.width
}
}
}
}
@Composable
fun Chip(
modifier: Modifier = Modifier,
text: String
) {
Card(
modifier = modifier,
border = BorderStroke(color = Color.Black, width = Dp.Hairline),
shape = RoundedCornerShape(8.dp)
) {
Row(
Modifier.padding(8.dp, 4.dp, 8.dp, 4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(16.dp, 16.dp)
.background(color = MaterialTheme.colorScheme.secondary)
)
Spacer(modifier = Modifier.width(4.dp))
Text(text = text)
}
}
}
@Composable
fun StaggeredGridBodyContent(modifier: Modifier = Modifier) {
Row(
modifier = modifier
.background(color = Color.LightGray)
.padding(16.dp)
.horizontalScroll(rememberScrollState()),
content = {
StaggeredGrid {
for (topic in topics) {
Chip(modifier = Modifier.padding(8.dp), text = topic)
}
}
}
)
}
约束布局
ConstraintLayout
是一种布局,让您可以相对于屏幕上的其他可组合项来放置可组合项。它是一种实用的替代方案,可代替使用多个已嵌套的Row、Column、Box
和其他自定义布局元素这种做法。在实现对齐要求比较复杂的较大布局时,ConstraintLayout
很有用。 在以下情况下,考虑使用 ConstraintLayout
: 为了避免在屏幕上定位元素时嵌套多个 Column
和 Row
,以便提高代码的可读性。 相对于其它可组合项来定位可组合项,或根据引导线、屏障线或链来定位可组合项。 在 View 系统中,建议使用 ConstraintLayout
来创建复杂的大型布局,因为扁平视图层次结构比嵌套视图的效果更好。
添加依赖
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"
@Composable
fun ConstraintLayoutContent2() {
ConstraintLayout {
val (button1, button2, text) = createRefs()
Button(
onClick = {},
modifier = Modifier.constrainAs(button1) {
top.linkTo(parent.top, margin = 16.dp)
}
) {
Text(text = "button 1")
}
Text(text = "text", Modifier.constrainAs(text) {
top.linkTo(button1.bottom, margin = 16.dp)
centerAround(button1.end)
})
Button(
onClick = {},
modifier = Modifier.constrainAs(button2) {
top.linkTo(button1.top)
start.linkTo(
text.end, margin = 10.dp
)
}
) {
Text(text = "button 1")
}
}
}
其中centerAround(button1.end)
表示、text的左右对称轴为button1的end
解耦API
上面的例子中约束条件是在应用它们的可组合项中使用修饰符以内嵌方式指定的。 不过,在某些情况下,最好将约束条件与应用它们的布局分离开来 解耦约束条件和布局需要运用: 将 ConstraintSet
作为参数传递给 ConstraintLayout
。 使用 layoutId
修饰符将在 ConstraintSet
中创建的引用分配给可组合项。
@Composable
fun DecoupledConstraintLayout() {
BoxWithConstraints {
val constraints = if (maxWidth < maxHeight) {
decoupledConstraints(margin = 16.dp) // Portrait constraints
} else {
decoupledConstraints(margin = 80.dp) // Landscape constraints
}
//将约束条件传递给布局
//以下是布局的实现
ConstraintLayout(constraints) {
Button(
onClick = { /* Do something */ },
//使用layoutId关联约束引用和可组合项
modifier = Modifier.layoutId("button")
) {
Text("Button")
}
Text("Text", Modifier.layoutId("text"))
}
}
}
//约束条件,返回是一个ConstraintSet
private fun decoupledConstraints(margin: Dp): ConstraintSet {
return ConstraintSet {
val button = createRefFor("button")
val text = createRefFor("text")
constrain(button) {
top.linkTo(parent.top, margin = margin)
}
constrain(text) {
top.linkTo(button.bottom, margin)
}
}
}
Intrinsics
我们使用如下代码实现的效果,整个Row的高度会受Divider的影响,尽可能填充最大高度
@Composable
fun TwoTexts(modifier: Modifier = Modifier) {
Row(modifier = modifier) {
Text(
text = "hi",
modifier = Modifier
.weight(1f)
.padding(start = 4.dp)
.wrapContentWidth(Alignment.Start)
)
Divider(
color = Color.Black,
modifier = Modifier
.fillMaxHeight()
.width(1.dp)
)
Text(
text = "there",
modifier = Modifier
.weight(1f)
.padding(start = 4.dp)
.wrapContentWidth(Alignment.End)
)
}
}
效果如下: 如果需要实现案例的效果,只需要使用IntrinsicSize.Min
,指定Row的高度就是所有子元素中最小的高度,因此fillMaxHeight
属性自动就是使用IntrinsicSize.Min
的值,也就是这里的两个Text
的高度
Row(modifier = modifier.height(IntrinsicSize.Min)) {