一、背景介绍
Jetpack Compose 是用于构建原生 Android 界面的新工具包。它使用更少的代码、强大的工具和直观的 Kotlin API,可以帮助您简化并加快 Android 界面开发。
2019年5月,Google在I/O大会上公布Compose
2020年9月,发布第一个Alpha版本
2021年7月,发布第一个稳定版本
1.1 主要内容
二、预备知识
2.1 默认参数
参数指定默认值。
/**
* 默认参数
*/
class DefaultParam {
companion object {
private const val TAG = "DefaultParam"
}
// 默认参数
fun test1(a: Int, b: String = "") {
Log.e(TAG, "test1: a:$a b:$b")
}
// 默认参数数据类
data class Test(val a: Int = 0, val b: Int)
fun test() {
// 方法最后参数提供了默认值,签名参数不需要指定形参名称 b,直接传值
test1(0)
// 命名参数可以忽略传参顺序
test1(
b = "test",
a = 2
)
// 构造方法第一个参数提供了默认值,第二个参数还是需要指定形参名称 b
Test(b = 2)
}
// 解构数据类,得到 a 和 b
fun testData() {
val test = Test(b = 2)
val (a, b) = test
Log.e(TAG, "a=$a")
Log.e(TAG, "b=$b")
}
}
函数参数很多时,可以分为必选参数和可选参数。命名参数可以不按照顺序传参。
举例:
文本控件只需要传入文字是什么。
Text API定义
@Composable
fun Text(
text: String,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current
){...}
2.2 高阶函数
参数是函数的函数。
/**
* 高阶函数
*/
class HigherFunction {
companion object {
private const val TAG = "HigherFunction"
}
fun high(one: (String) -> Unit, string: String) {
one(string)
// one.invoke(string)
}
fun high2(two: () -> Unit) {
two()
}
fun test() {
high({
Log.e(TAG, "test: string: $it")
}, "test")
}
fun test2() {
// 尾随lambda:函数的最后一个参数是函数类型(lambda)时,可以把 {} 写在函数外部。
// 如果除lambda之外没有其他参数,可以省略圆括号
high2 {
Log.e(TAG, "test2: string")
}
}
}
举例:
Column布局,最后一个参数 content 是函数类型。
@Composable
inline fun Column(
modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
content: @Composable ColumnScope.() -> Unit
) {...}
使用 Column
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
// 3 ImageView
Image(
modifier = Modifier
.size(LOGO_HEIGHT)
.alpha(alpha = alphaState),
painter = painterResource(id = getLogo()),
contentDescription = stringResource(id = R.string.compose)
)
// 4 TextView
Text(
text = stringResource(id = R.string.compose),
color = MaterialTheme.colors.splashText,
style = MaterialTheme.typography.h5,
fontWeight = FontWeight.Bold,
maxLines = 1
)
}
三、第一个Compose程序
3.1 HelloWorld
class ComposeMainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Greeting(name = "Compose")
}
}
@Composable
fun Greeting(name: String) {
Text(
text = "Hello $name"
)
}
}
3.2 预览
使用 @Preview 预览,可以同时存在多个预览 。
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
Greeting(name = "World")
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview2() {
Greeting(name = "Compose")
}
交互式预览
@Composable
fun Counter(count: Int, updateCount: (Int) -> Unit) {
Button(
onClick = { updateCount(count + 1) },
colors = ButtonDefaults.buttonColors(
backgroundColor = if (count > 5) Color.Green else Color.White
)
) {
Text("I've been clicked $count times")
}
}
@Preview
@Composable
fun PreviewCounter() {
// mutableStateOf 使 counterState 的值能被 Compose 观察到
// remember 记住 counterState 当前值
val counterState = remember { mutableStateOf(0) }
Counter(
count = counterState.value,
updateCount = { newCount ->
counterState.value = newCount
}
)
}
点击1次
点击6次
四、简单控件
4.1 文本
Text(
text = stringResource(id = R.string.compose),
color = MaterialTheme.colors.splashText,
style = MaterialTheme.typography.h5,
fontWeight = FontWeight.Bold,
maxLines = 1
)
4.2 输入框
val text = remember { mutableStateOf("你好") }
TextField(
value = text.value,
onValueChange = { text.value = it },
label = { Text("标签") }
)
4.3 按钮
val i = remember {
mutableStateOf(0)
}
Button(onClick = {
i.value++
}) {
Text(
text = "按钮 ${i.value}"
)
}
4.4 图片
Image(
modifier = Modifier
.size(LOGO_HEIGHT),
painter = painterResource(id = getLogo()),
contentDescription = stringResource(id = R.string.compose)
)
4.5 进度条
// 圆形进度条
CircularProgressIndicator()
五、布局
5.1 线性布局
Column 纵向线性布局
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text("One")
Text("Two")
Text("Three")
}
Row 横向线性布局
Row(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
Text("One")
Text("Two")
Text("Three")
}
5.2 盒布局
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Row {
Box(
contentAlignment = Alignment.TopStart,
modifier = Modifier
.size(100.dp)
.background(Color.Gray),
) {
Text("1", fontSize = 20.sp)
}
Box(
contentAlignment = Alignment.TopCenter,
modifier = Modifier
.size(100.dp)
.background(Color.Magenta),
) {
Text("2", fontSize = 20.sp)
}
Box(
contentAlignment = Alignment.TopEnd,
modifier = Modifier
.size(100.dp)
.background(Color.Cyan),
) {
Text("3", fontSize = 20.sp)
}
}
Row {
Box(
contentAlignment = Alignment.CenterStart,
modifier = Modifier
.size(100.dp)
.background(Color.DarkGray),
) { Text("4", fontSize = 20.sp) }
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(100.dp)
.background(Color.Green),
) {
Text("5", fontSize = 20.sp)
}
Box(
contentAlignment = Alignment.CenterEnd,
modifier = Modifier
.size(100.dp)
.background(Color.Red)
) {
Text(
"6",
fontSize = 20.sp
)
}
}
Row {
Box(
contentAlignment = Alignment.BottomStart,
modifier = Modifier
.size(100.dp)
.background(Color.Magenta),
) { Text("7", fontSize = 20.sp) }
Box(
contentAlignment =
Alignment.BottomCenter,
modifier = Modifier
.size(100.dp)
.background(Color.Yellow),
) {
Text(
"8",
fontSize = 20.sp
)
}
Box(
contentAlignment = Alignment.BottomEnd,
modifier = Modifier
.size(100.dp)
.background(Color.Magenta),
) {
Text("9", fontSize = 20.sp)
}
}
}
每一行是 3 个 Box,使用 contentAlignment 控制内容文本的对齐方式。
5.3 约束布局
ConstraintLayout
@Preview(showBackground = true)
@Composable
fun Preview() {
ConstraintLayout(modifier = Modifier.fillMaxSize()) {
val (one, two) = createRefs()
val three = createRef()
DefaultText(
"One",
modifier = Modifier.constrainAs(one) {
start.linkTo(parent.start)
end.linkTo(parent.end)
top.linkTo(parent.top, margin = 16.dp)
})
DefaultText(
"Two",
Modifier.constrainAs(two) {
start.linkTo(parent.start)
end.linkTo(parent.end)
top.linkTo(one.bottom, margin = 16.dp)
})
DefaultText(
"Three",
Modifier.constrainAs(three) {
start.linkTo(parent.start)
end.linkTo(parent.end)
bottom.linkTo(parent.bottom, margin = 16.dp)
})
}
}
@Composable
fun DefaultText(text: String, modifier: Modifier) {
Text(
text,
modifier = modifier
.size(100.dp)
.background(Color.Red),
fontSize = 30.sp,
textAlign = TextAlign.Center
)
}
5.4 修饰符
Modifier
内边距 padding
@Preview(showBackground = true)
@Composable
fun Preview() {
Text("Zhujiang", modifier = Modifier.padding(50.dp))
}
@Preview(showBackground = true)
@Composable
fun Preview2() {
Text("Zhujiang2", modifier = Modifier.padding(0.dp))
}
控件尺寸
@Preview(showBackground = true)
@Composable
fun Preview() {
Text("Zhujiang", modifier = Modifier.fillMaxSize())
}
@Preview(showBackground = true)
@Composable
fun Preview2() {
Text("Zhujiang", modifier = Modifier.fillMaxWidth())
}
当什么都不设置的时候,就是自适应大小。
@Preview(showBackground = true)
@Composable
fun Preview() {
Text("Zhujiang", modifier = Modifier.size(width = 100.dp, height = 110.dp))
}
@Preview(showBackground = true)
@Composable
fun Preview2() {
Text("Zhujiang")
}
weight 权重
@Preview(showBackground = true)
@Composable
fun Preview() {
Row(
Modifier
.fillMaxSize()
.padding(top = 10.dp)
) {
Box(
Modifier
.weight(2f)
.height(50.dp)
.background(Color.Blue)
)
Box(
Modifier
.weight(1f)
.height(50.dp)
.background(Color.Red)
)
}
}
点击事件
@Preview(showBackground = true)
@Composable
fun Preview() {
val context = LocalContext.current
Text(
"Click",
modifier = Modifier
.background(Color.Green)
.clickable {
// 执行点击事件需要的操作
Toast
.makeText(context, "click it", Toast.LENGTH_SHORT)
.show()
},
fontSize = 30.sp,
textAlign = TextAlign.Center
)
}
5.5 脚手架
Scaffold 脚手架,它实现了基本的 MaterialDesign 可视化的布局结构,提供了用于显示 drawer(侧边抽屉)、topbar、snackbar 和底部导航栏的 API。
@Preview(showBackground = true)
@Composable
fun Preview() {
Scaffold(
topBar = {
/*.标题栏..*/
TopAppBar(
title = { Text("标题") },
navigationIcon = {
IconButton(onClick = { /*.点击事件..*/ })
{
Icon(Icons.Filled.ArrowBack, "")
}
})
},
floatingActionButton = { /*.悬浮按钮..*/
FloatingActionButton(onClick = { // Floating点击事件
}) {
Text("OK")
}
},
content = { /*.主内容..*/
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text("主屏幕", fontSize = 40.sp)
}
}
)
}
六、复杂控件
6.1 列表
纵向列表 LazyColumn
@Preview(showBackground = true, widthDp = 200, heightDp = 400)
@Composable
fun Preview() {
// 准备数据
val dataList = arrayListOf<Int>()
for (index in 0..10) {
dataList.add(index)
}
LazyColumn {
items(dataList) { data ->
// item布局
Text("item:$data")
}
}
}
多类型
@Preview(showBackground = true, widthDp = 200, heightDp = 400)
@Composable
fun Preview() {
// 准备数据
val charList = arrayListOf<Chat>().apply {
add(Chat("你好啊"))
add(Chat("你在干啥呢"))
add(Chat("想问你个事"))
add(Chat("没干啥,还在写代码啊!", false))
add(Chat("啥事啊大哥?", false))
add(Chat("没事。。。"))
add(Chat("好吧。。。", false))
}
LazyColumn {
items(charList) { data ->
// 1. 左边消息样式
if (data.isLeft) {
Column(modifier = Modifier.padding(end = 20.dp)) {
Spacer(modifier = Modifier.height(5.dp))
Text(
data.content, modifier = Modifier
.fillMaxWidth()
.height(25.dp)
.background(Color.Magenta)
)
}
} else {
// 2. 右边消息样式
Column(modifier = Modifier.padding(start = 20.dp)) {
Spacer(modifier = Modifier.height(5.dp))
Text(
data.content, modifier = Modifier
.fillMaxWidth()
.background(Color.Green)
.height(25.dp)
)
}
}
}
}
}
6.2 网格
LazyVerticalGrid
@ExperimentalFoundationApi
@Preview(showBackground = true, widthDp = 200, heightDp = 400)
@Composable
fun Preview() {
// 准备数据
val photos = arrayListOf<Int>()
for (index in 0..20) {
photos.add(R.drawable.ic_launcher_background)
}
LazyVerticalGrid(
// 自适应宽度
cells = GridCells.Adaptive(minSize = 60.dp)
) {
items(photos) { photo ->
Image(
painter = painterResource(
photo
),
"",
modifier = Modifier.padding(2.dp)
)
}
}
}
6.3 底部导航栏
BottomNavigation
enum class Tabs(
val title: String, @DrawableRes val icon: Int
) {
ONE("One", R.drawable.ic_nav_news_normal),
TWO("Two", R.drawable.ic_nav_tweet_normal),
THREE("Three", R.drawable.ic_nav_discover_normal),
FOUR("Fore", R.drawable.ic_nav_my_normal)
}
@Composable
fun One() {
BaseDefault("One")
}
@Composable
fun Two() {
BaseDefault("Two")
}
@Composable
fun Three() {
BaseDefault("Three")
}
@Composable
fun Four() {
BaseDefault("Four")
}
@Composable
fun BaseDefault(content: String) {
Row(
modifier = Modifier.fillMaxSize(),
horizontalArrangement =
Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) { Text(content, fontSize = 50.sp) }
}
@ExperimentalFoundationApi
@Preview(showBackground = true, widthDp = 200, heightDp = 400)
@Composable
fun Preview() {
// Tab数据
val tabs = Tabs.values()
// 使用remember记住State值
var position by remember { mutableStateOf(Tabs.ONE) }
// 脚手架,方便实现页面
Scaffold(
backgroundColor = Color.Yellow,
// 定义bottomBar
bottomBar = {
BottomNavigation {
tabs.forEach { tab ->
BottomNavigationItem(
modifier = Modifier.background(MaterialTheme.colors.primary),
icon = { Icon(painterResource(tab.icon), contentDescription = null) },
label = { Text(tab.title) },
selected = tab == position,
onClick = { position = tab },
alwaysShowLabel = false,
)
}
}
}) {
// 根据State值的变化来动态切换当前显示的页面
when (position) {
Tabs.ONE -> One()
Tabs.TWO -> Two()
Tabs.THREE -> Three()
Tabs.FOUR -> Four()
}
}
}