Jetpack Compose常用UI组件实战演练

一、前言

这是 Jetpack Compose 系列的第五篇。前面四篇我们介绍了Compose的背景、完成了第一个Compose程序理解了Composable与Modifier的核心机制,还深入学习了Modifier的各种修饰符。今天,让我们用实战的方式,系统学习Compose中最常用的UI组件。

Compose的组件体系和传统的 Android View 一样:布局组件 决定UI怎么排列,基础组件 决定UI长什么样,列表组件决定大量数据怎么展示。下面逐一展示给大家。

二、布局容器

传统 Android UI开发用 XML 写布局(LinearLayoutRelativeLayoutFrameLayout),Compose 则使用Kotlin函数描述布局。最常用的三个容器是 ColumnRow Box

Column------纵向排列,子组件从上到下依次摆放

kotlin 复制代码
@Composable
fun ColumnExample() {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        Text("北京")
        Text("上海")
        Text("广州")
    }
}

Row------横向排列,子组件从左到右依次摆放。

kotlin 复制代码
@Composable
fun RowExample() {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        horizontalArrangement = Arrangement.SpaceEvenly
    ) {
        Text("左")
        Text("中")
        Text("右")
    }
}

Box------层叠布局,子组件堆叠在一起,类似传统View 体系的FrameLayout

kotlin 复制代码
@Composable
fun BoxExample() {
    Box(
        modifier = Modifier.size(150.dp),
        contentAlignment = Alignment.Center
    ) {
        Box(modifier = Modifier
            .fillMaxSize()
            .background(Color.Yellow))
        Text("文字居中", color = Color.White)
    }
}

💡Column 和 Row负责"排列",Box 负责"叠加"。三者配合可以构建绝大多数界面布局。

布局效果如下:

三、基础组件

3.1 Text

Text 文本组件,用来显示文本,替代传统的TextView,支持丰富的样式配置。如:

  • fontWeight:文本的粗细
  • fontFamily:文本的字体
  • fontStyle:文本的变体(Italic/Normal)
  • lineHeight:行高
  • ...

除了直接给 Text 传入要显示的文字内容外,Compose 同样支持 R资源 的方式获取字符串------stringResource()

kotlin 复制代码
@Composable
fun TextExample() {
    // 指定字符串
    Text(
        text = "Hello Compose!",
        fontSize = 20.sp,
        fontWeight = FontWeight.Bold,
        color = Color.Blue,
        text = TextAlign.Center,
        maxLines = 1,
        modifier = Modifier.padding(16.dp)
    )
    
    // 指定文字资源
    Text(
     	text = stringResource(R.string.hello_world)
    )
}

3.2 Image

Image 图片组件,显示图片,替代传统的 ImageView。支持3种类型的图片设置:

  • ImageVector:矢量图对象,可以显示SVG格式的图
  • ImageBitmap:位图对象,可以显示JPG、PNG等格式图
  • Painter:自定义画笔,可以在Canvas上直接绘制图

Image 组件支持直接传递实例参数,但一般开发中我们优先使用 res/drawable 下的资源图。

kotlin 复制代码
@Composable
fun ImageSample() {
    Column() {
        Image(
            painter = painterResource(id = R.drawable.ic_weather_sun),
            contentDescription = "晴",
        )
        Image(
            bitmap = ImageBitmap.imageResource(id = R.drawable.pic_2),
            contentDescription = null
        )
        // 本地图片放在 res/drawable 目录下,通过 painterResource 加载的示例。
        Image(
            imageVector = ImageVector.vectorResource(id = R.drawable.ic_search),
            contentDescription = "矢量图资源"
        )

    }
}

图片显示效果如下:

熟悉传统 Android View的小伙伴肯定熟悉 scaleType,它用来指定图片的伸缩样式(拉伸、裁剪、留白还是保持原样),Compose的 Image 同样有提供这样的功能------contentScale参数

ContentScale类型 说明
ContentScale.Crop 按比例缩放使图片完全覆盖容器,超出部分裁剪,类似ScaleType.centerCrop
ContentScale.Fit 按比例缩放使图片完整显示在容器内,居中,类似ScaleType.fitCenter
ContentScale.Inside 完整显示且不放大,仅当图片大于容器时缩小,类似ScaleType.centerInside
ContentScale.FillBounds 不保持比例拉伸填满容器,类似ScaleType.fitXY
ContentScale.FillWidth Compose独有 ,按比例缩放使图片宽度填满容器,高度自适应
ContentScale.FillHeight Compose独有 ,按比例缩放使图片高度填满容器,宽度自适应
ContentScale.None 不缩放,原尺寸居中绘制,过大裁剪/过小留白,类似ScaleType.center

3.3 Button

Button 按钮组件,处理点击交互用,Material 3 提供了多种的按钮样式。

kotlin 复制代码
@Composable
fun ButtonExample() {
    var clickCount by remember { mutableStateOf(0) }
    
    Button(
        onClick = { clickCount++ },
        modifier = Modifier.padding(16.dp)
    ) {
        Text(
            text = "点击次数: $clickCount",
            fontSize = 18.sp,
            modifier = Modifier.padding(2.dp)
        )
    }
}

Button 的点击事件通过 onClick 参数传入,组件内部状态用remember { mutableStateOf()} 管理。

四、列表

LazyColumn ------高性能列表,类似传统Android View中的 RecyclerView

kotlin 复制代码
@Composable
fun LazyColumnExample() {
    val items = List(50) { "条目 #$it" }
    
    LazyColumn(modifier = Modifier.fillMaxSize()) {
        items(items) { item ->
            Text(
                text = item,
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp)
            )
        }
    }
}

运行代码效果如下:

五、页面骨架

Scaffold ------ Material Design 页面骨架,内置了TopAppBarFloatingActionButtonNavigationBar等。

kotlin 复制代码
@Composable
fun ScaffoldExample() {
    var fabClickCount by remember { mutableStateOf(0) }
    
    Scaffold(
        topBar = {
            TopAppBar(title = { Text("我的应用") })
        },
        floatingActionButton = {
            FloatingActionButton(onClick = { fabClickCount++ }) {
                Icon(Icons.Default.Add, contentDescription = "添加")
            }
        }
    ) { innerPadding ->
        Box(
            modifier = Modifier
                .fillMaxSize()
                .padding(innerPadding),
            contentAlignment = Alignment.Center
        ) {
            Text("FAB点击: $fabClickCount 次")
        }
    }
}

效果示意图如下:

六、综合实践

看了这么多单个组件用法,下面让我们来完成综合学习------使用上面提到的组件实现一个天气主界面:

kotlin 复制代码
private val city = "北京"
private val currentTemp = "28°C"
private val weatherCondition = "晴"
private val feelsLike = "26°C"
private val humidity = "65%"
private val wind = "12 km/h"

private val hourlyList = listOf(
    "现在 28°", "13:00 29°", "14:00 30°", "15:00 29°", "16:00 28°"
)

private val dailyList = listOf(
    "今天 30°/22° 晴",
    "周一 28°/20° 多云",
    "周二 26°/19° 小雨",
    "周三 27°/21° 晴",
    "周四 29°/23° 多云",
    "周五 31°/24° 晴",
    "周六 30°/22° 阴"
)

@Composable
fun SimpleWeatherScreen() {
    Scaffold { innerPadding ->
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(innerPadding)
                .padding(16.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceBetween,
                verticalAlignment = Alignment.CenterVertically
            ) {
                Text(
                    text = city,
                    fontSize = 24.sp,
                    fontWeight = FontWeight.Bold
                )
            }

            Spacer(modifier = Modifier.height(20.dp))

            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(150.dp),
                contentAlignment = Alignment.Center
            ) {
                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                    Image(
                        painter = painterResource(id = android.R.drawable.ic_menu_gallery),
                        contentDescription = "天气图标",
                        modifier = Modifier.size(60.dp)
                    )
                    Text(
                        text = currentTemp,
                        fontSize = 40.sp,
                        fontWeight = FontWeight.Bold
                    )
                    Text(text = weatherCondition, fontSize = 18.sp)
                }
            }

            Spacer(modifier = Modifier.height(16.dp))

            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceEvenly
            ) {
                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                    Text(text = feelsLike, fontWeight = FontWeight.Bold)
                    Text(text = "体感", fontSize = 12.sp, color = Color.Gray)
                }
                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                    Text(text = humidity, fontWeight = FontWeight.Bold)
                    Text(text = "湿度", fontSize = 12.sp, color = Color.Gray)
                }
                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                    Text(text = wind, fontWeight = FontWeight.Bold)
                    Text(text = "风速", fontSize = 12.sp, color = Color.Gray)
                }
            }

            Spacer(modifier = Modifier.height(20.dp))

            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceEvenly
            ) {
                hourlyList.take(5).forEach { item ->
                    Column(horizontalAlignment = Alignment.CenterHorizontally) {
                        Text(text = item.split(" ")[0], fontSize = 12.sp)
                        Image(
                            painter = painterResource(id = android.R.drawable.ic_menu_gallery),
                            contentDescription = null,
                            modifier = Modifier.size(24.dp)
                        )
                        Text(text = item.split(" ")[1], fontSize = 12.sp)
                    }
                }
            }

            Spacer(modifier = Modifier.height(20.dp))

            LazyColumn(
                modifier = Modifier.fillMaxSize(),
                verticalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                items(dailyList) { dayInfo ->
                    val parts = dayInfo.split(" ")
                    val day = parts[0]
                    val temp = parts[1]
                    val condition = parts[2]
                    Row(
                        modifier = Modifier.fillMaxWidth(),
                        horizontalArrangement = Arrangement.SpaceBetween
                    ) {
                        Text(text = day, fontWeight = FontWeight.Medium)
                        Row {
                            Text(text = condition)
                            Spacer(modifier = Modifier.width(8.dp))
                            Text(text = temp)
                        }
                    }
                }
            }
        }
    }
}

感兴趣的读者可以直接Copy上面的代码在 AndroidStudio 里运行,最后的效果类似下面:

七、 总结

本篇内容覆盖了 Compose 日常开发中最核心的几类组件:布局容器(Column/Row/Box)基础组件(Text/Image/Button)列表(LazyColumn)页面骨架(Scaffold)

掌握了这些组件后足以支撑起一个简单但完整APP的UI开发,如待办事项APP、天气APP、新闻APP等。

🚨 最后提醒,本文示例使用的为 Material Design 3

八、参考资料