17、Compose布局:Column、Row、Box、ConstraintLayout、LazyColumn、实战:复杂布局实现。

布局,是UI的骨架。在Jetpack Compose里,布局的玩法跟传统View体系完全不同。你不再写XML,而是用Kotlin代码直接描述UI。刚开始我也有点不习惯,但用顺手之后,真香。

今天这一章,咱们就把Compose里最核心的几个布局组件掰开揉碎。Column、Row、Box、ConstraintLayout、LazyColumn,再加上一个复杂布局的实战。嗯,内容不少,但都是干货。

17.1 Column:纵向排列,最常用的布局

Column就是垂直方向的线性布局。说白了,它把子组件从上到下依次排开。我在项目中用它来搭建列表页、表单页、详情页,几乎每天都在用。

@Composable
fun ColumnDemo() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("第一行")
        Text("第二行")
        Text("第三行")
    }
}
小技巧:verticalArrangement控制纵向间距,horizontalAlignment控制横向对齐。我个人习惯把spacedBy设为8.dp或16.dp,视觉上最舒服。

17.2 Row:横向排列,跟Column是对称的

Row就是水平方向的线性布局。它把子组件从左到右依次排开。你想想看,导航栏、标签栏、操作按钮组,这些场景都离不开Row。

@Composable
fun RowDemo() {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(8.dp),
        horizontalArrangement = Arrangement.SpaceEvenly,
        verticalAlignment = Alignment.CenterVertically
    ) {
        Icon(Icons.Default.Home, contentDescription = null)
        Icon(Icons.Default.Favorite, contentDescription = null)
        Icon(Icons.Default.Settings, contentDescription = null)
    }
}
注意:Row和Column的排列方向不同,但参数结构是对称的。我曾经在项目里把horizontalArrangement和verticalAlignment搞反了,结果UI乱成一团。记住:Row的“水平”是排列方向,“垂直”是对齐方向。

17.3 Box:层叠布局,像FrameLayout

Box允许子组件在Z轴上层叠。说白了,就是可以一个组件叠在另一个组件上面。我经常用它来实现角标、遮罩层、水印效果。

@Composable
fun BoxDemo() {
    Box(
        modifier = Modifier.size(200.dp),
        contentAlignment = Alignment.Center
    ) {
        Image(
            painter = painterResource(id = R.drawable.bg),
            contentDescription = null,
            modifier = Modifier.fillMaxSize()
        )
        Text(
            text = "Hello",
            color = Color.White,
            fontSize = 24.sp
        )
    }
}
核心要点:Box的contentAlignment控制所有子组件的默认对齐方式。如果某个子组件需要单独对齐,可以用Modifier.align()覆盖。

17.4 ConstraintLayout:复杂布局的利器

当布局层级超过3层,或者组件之间有多重依赖关系时,Column和Row就不够用了。这时候我会掏出ConstraintLayout。它跟View体系里的ConstraintLayout思路一致,但写法更简洁。

@Composable
fun ConstraintLayoutDemo() {
    ConstraintLayout(
        modifier = Modifier.fillMaxSize()
    ) {
        val (button, text, image) = createRefs()

        Button(
            onClick = { },
            modifier = Modifier.constrainAs(button) {
                top.linkTo(parent.top, margin = 16.dp)
                start.linkTo(parent.start)
                end.linkTo(parent.end)
            }
        ) {
            Text("点击")
        }

        Text(
            text = "描述文字",
            modifier = Modifier.constrainAs(text) {
                top.linkTo(button.bottom, margin = 8.dp)
                start.linkTo(parent.start)
                end.linkTo(parent.end)
            }
        )

        Image(
            painter = painterResource(id = R.drawable.ic_launcher),
            contentDescription = null,
            modifier = Modifier.constrainAs(image) {
                top.linkTo(text.bottom, margin = 16.dp)
                start.linkTo(parent.start)
                end.linkTo(parent.end)
            }
        )
    }
}
经验之谈:用createRefs()一次性声明所有引用,然后用constrainAs()给每个组件绑定约束。我曾经试过逐个声明,代码又长又乱。后来改成批量声明,清爽多了。

17.5 LazyColumn:高性能列表

列表是移动端最常用的UI形态。LazyColumn就是Compose里的RecyclerView。它只渲染可见项,性能极好。我在项目里用它来展示聊天记录、商品列表、新闻流。

@Composable
fun LazyColumnDemo() {
    val items = (1..100).toList()

    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(items) { item ->
            Card(
                modifier = Modifier.fillMaxWidth(),
                elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
            ) {
                Text(
                    text = "第 $item 项",
                    modifier = Modifier.padding(16.dp)
                )
            }
        }
    }
}
避坑指南:LazyColumn的items()函数里不要用Modifier.weight()。我曾经踩过这个坑,列表项死活不按预期显示。后来查文档才知道,LazyColumn不支持weight,因为它的子项高度是动态计算的。

17.6 实战:复杂布局实现

光说不练假把式。咱们来做一个真实场景:一个商品详情页。它包含顶部图片、标题、价格、规格选择、评价列表。这个布局用单一组件搞不定,得组合使用。

@Composable
fun ProductDetailScreen() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .verticalScroll(rememberScrollState())
    ) {
        // 顶部图片
        Image(
            painter = painterResource(id = R.drawable.product),
            contentDescription = null,
            modifier = Modifier
                .fillMaxWidth()
                .height(300.dp)
        )

        // 标题和价格
        Column(modifier = Modifier.padding(16.dp)) {
            Text(
                text = "高端机械键盘",
                fontSize = 24.sp,
                fontWeight = FontWeight.Bold
            )
            Text(
                text = "¥299.00",
                color = Color.Red,
                fontSize = 20.sp,
                modifier = Modifier.padding(top = 8.dp)
            )
        }

        // 规格选择
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = 16.dp),
            horizontalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            Chip(text = "黑色")
            Chip(text = "白色")
            Chip(text = "蓝色")
        }

        // 评价列表
        LazyColumn(
            modifier = Modifier
                .fillMaxWidth()
                .height(200.dp),
            contentPadding = PaddingValues(16.dp),
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            items(5) { index ->
                Text("用户评价 $index:手感很好,推荐购买!")
            }
        }
    }
}

@Composable
fun Chip(text: String) {
    Surface(
        shape = RoundedCornerShape(16.dp),
        color = Color.LightGray,
        modifier = Modifier.padding(4.dp)
    ) {
        Text(
            text = text,
            modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp)
        )
    }
}
设计思路:外层用Column做纵向滚动,内部嵌套Row做横向排列,评价区域用LazyColumn做高性能列表。这样既保证了整体可滚动,又让评价列表独立回收,性能拉满。

17.7 知识体系总览

为了让你更直观地理解这些布局组件的关系,我画了一张图。它展示了每个组件的核心用途和适用场景。

Compose 布局组件知识体系 Compose 布局 Column Row Box ConstraintLayout LazyColumn 实战组合 Column:纵向排列 Row:横向排列 Box:层叠布局 ConstraintLayout:复杂约束 LazyColumn:高性能列表

17.8 总结与建议

Compose的布局体系,说白了就是几个核心组件的灵活组合。Column和Row解决线性排列,Box解决层叠,ConstraintLayout解决复杂约束,LazyColumn解决长列表。你想想看,大部分UI场景都能用这五个组件搞定。

我在项目里总结了一个原则:能用Column/Row解决的,绝不用ConstraintLayout。因为Column/Row的代码更简洁,性能也更好。只有当布局层级超过3层,或者组件之间有交叉依赖时,才考虑ConstraintLayout。

最后一个小建议:多写多练。布局这东西,看十遍不如写一遍。你可以在自己的项目里试着把旧的XML布局改成Compose,改完之后你会对Compose的布局哲学有更深的理解。

公众号:蓝海资料掘金营,微信deep3321