Jetpack Compose是Android推出的新一代声明式UI框架,随着alpha版本的发布,其API也逐渐趋于稳定,是时候深入学习一波了。
Compose借鉴了React的设计思想,配合Kotlin函数式编程的语法特性,通过函数声明UI组件:
@Composable
fun AnyUiComponent() {
// Code for UI element
}
而开发者唯一需要做的是添加@Composable
注解,它会在编译期将创建真正的Composer对象参与底层渲染逻辑。
本文不针对底层原理作介绍,主要是向大家安利一个Compose项目
Learn Jetpack Compose for Android by example
通过例子快速上手Compose
本文基于上述内容做一个摘要介绍,帮助大家更快速地了解和熟悉Compose的基本使用
@Composable
fun SimpleText(displayText: String) {
Text(text = displayText)
}
我们使用@Composable
创建了一个自定义组件SimpleText
,其内部嵌套调用了Compose内置组件Text,并向其传递参数
完成SimpleText的定义后,我们便可以在Activity中使用
class SimpleTextActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
SimpleText(getString("I am learning Compose"))
}
}
}
}
作为声明式UI框架,Compose与React、Flutter等的使用方式类似,都是通过声明+组合的方式实现UI的搭建。如上,我们在Column中调用了我们自定义的SimpleText,用近似写XML的方式写Kotlin代码,这就是声明式的魅力。
通过为Text指定Style,可以设置字体、颜色等。
@Composable
fun StyleText(displayText: String, style: TextStyle? = null, maxLines: Int? = null) {
Text(
text = displayText,
modifier = Modifier.padding(16.dp),
style = style ?: TextStyle.Default,
overflow = TextOverflow.Ellipsis,
maxLines = maxLines ?: Int.MAX_VALUE
)
}
上例中定义了一个StyleText组件,内部通过Text的参数设置样式,例如:
style = TextStyle(
fontSize = 24.sp
)
fontWeight = FontWeight.Bold
Compose使用BaseTextField
实现文本输入,类似Android的EditText
。BaseTextField
是不稳定的API,使用时需要添加@ExperimentalFoundationApi
@ExperimentalFoundationApi
@Composable
fun SimpleTextFieldComponent() {
Surface(color = Color.LightGray, modifier = Modifier.padding(16.dp)) {
var text by remember { mutableStateOf(TextFieldValue("Enter text here")) }
BaseTextField(
value = text,
modifier = Modifier.padding(16.dp).fillMaxWidth(),
onValueChange = {
text = it
}
)
}
}
其中onValueChange
响应文本框的输入更新。
另外,还可以使用 Material Design还提供的基于BaseTextField的默认实现TextField
@Composable
fun SimpleMaterialTextFieldComponent() {
var text by savedInstanceState { "" }
TextField(
value = text,
modifier = Modifier.padding(16.dp).fillMaxWidth(),
onValueChange = { text = it },
label = { Text("Label") }
)
}
当没有输入内容时,label
通过Text提供默认文本显示,相当于hint
我们可以通过参数对TextField或者BaseTextField进行配置,例如:
var text by remember { mutableStateOf(TextFieldValue("0123")) }
BaseTextField(value = text,
keyboardType = KeyboardType.Number,
onValueChange = {
text = it
}
)
keyboardType = KeyboardType.Password,
visualTransformation = PasswordVisualTransformation()
placeholder = { Text("MindOrks") }
更多参数的使用,例如icon、errorColor 、backgroundColor等可以参考项目中的示例
最新的AndroidStudio支持基于Compose的布局预览(手头版本 AS 4.1 Preview)
// This is a Composable function to display a Text
@Composable
fun SimpleText(displayText: String) {
Text(text = displayText)
}
@Preview
@Composable
fun SimpleTextPreview() {
SimpleText("Hi I am learning Compose")
}
一个简单的可预览的Composable函数需满足以下条件:
@Preiview
注解因为预览的本质就是自动运行一下Compose函数,所以无法运行带有参数的函数。添加@Preview后,便可以在Studio中实时预览当前效果。
当有同时预览多个组件时,为了便于区分可以为其指定name
@Preview(name = "Named Preview")
通常的预览不支持传入参数,通过使用@PreviewParameter
也可以实现参数预览,准备过程会繁琐一些:
data class Blog(
val name: String,
val author: String
)
class DummyBlogProvider : PreviewParameterProvider<Blog> {
override val values =
sequenceOf(Blog("Learning Compose", "MindOrks"), Blog("Learning Android", "MindOrks"))
override val count: Int = values.count()
}
@Preview
@Composable
fun BlogInfo(@PreviewParameter(DummyBlogProvider::class) blog: Blog) {
SimpleTextComponent("${blog.name} by ${blog.author}")
}
Column类似一个Vertical的LinearLayout
@Composable
fun SimpleColumnComponent() {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = "Hello! I am Text 1", color = Color.Black)
Text(text = "Hello! I am Text 2", color = Color.Blue)
}
}
当Column中的内容超出屏幕高度时,需要使用ScrollableColumn
,相当于ScrollView
@Composable
fun ScrollableColumnComponent(blogList: List<Blog>) {
ScrollableColumn {
val context = ContextAmbient.current
Column {
for (blog in blogList) {
Card(
shape = RoundedCornerShape(4.dp),
modifier = Modifier.fillMaxWidth().padding(16.dp).clickable(onClick = {
Toast.makeText(context, "Author: ${blog.author}", Toast.LENGTH_SHORT).show()
}),
backgroundColor = Color(0xFFFFA867.toInt())
) {
Text(
blog.name, style = TextStyle(
fontSize = 16.sp,
textAlign = TextAlign.Center
), modifier = Modifier.padding(16.dp)
)
}
}
}
}
}
LazyColumnFor
可以当item进入屏幕时再加载,而无需一次加载全部数据,相当于RecyclerView
@Composable
fun LazyColumnScrollableComponent(blogList: List<Blog>) {
LazyColumnFor(items = blogList, modifier = Modifier.fillMaxHeight()) { blog ->
val context = ContextAmbient.current
Card(
shape = RoundedCornerShape(4.dp),
modifier = Modifier.fillParentMaxWidth().padding(16.dp).clickable(onClick = {
Toast.makeText(context, "Author: ${blog.author}", Toast.LENGTH_SHORT).show()
}),
backgroundColor = Color(0xFFFFA867.toInt())
) {
Text(
blog.name, style = TextStyle(
fontSize = 16.sp,
textAlign = TextAlign.Center
), modifier = Modifier.padding(16.dp)
)
}
}
}
相应的,在水平方向有Row
、ScrollableRow
、Lazy Row
可供选择。
Box内部的子控件会在Z轴上按顺序叠加,类似FrameLayout。在Flutter中类似的Widget称为Stack,Compose早期的API中也叫做Stack,如今已废弃被Box取代。
@Composable
fun SimpleBoxComponent() {
Box(modifier = Modifier.fillMaxSize().padding(16.dp)) {
Image(imageResource(R.drawable.mindorks_cover))
Text(
modifier = Modifier.padding(start = 16.dp, top = 16.dp),
text = "I am a text over Image",
fontSize = 16.sp,
color = Color.Red
)
}
}
@Composable
fun SimpleButtonComponent() {
val context = ContextAmbient.current
Button(
onClick = {
Toast.makeText(context, "Thanks for clicking!", Toast.LENGTH_LONG).show()
},
modifier = Modifier.padding(8.dp).fillMaxWidth()
) {
Text("Click Me")
}
}
如上,Button内部摆放Text用来显示按钮的文字,onClick
响应点击事件
通过参数可以进行其他配置,例如:
shape = RoundedCornerShape(12.dp)
border = BorderStroke(width = 1.dp, brush = SolidColor(Color.Green))
更多参数的使用,例如icon、color 等可以参考项目中的示例
Card相当于Material中的CardView
@Composable
fun SimpleCardComponent() {
Card(
backgroundColor = Color(0xFFFFA867.toInt()),
modifier = Modifier.padding(16.dp).fillMaxWidth()
) {
Text(
text = "Simple Card",
textAlign = TextAlign.Center,
style = TextStyle(
fontSize = 16.sp
),
modifier = Modifier.padding(16.dp)
)
}
}
你可以为任意Compose组件添加Click功能,并支持单击、双击、长按等多种点击效果
@Composable
fun SimpleTextComponent() {
val context = ContextAmbient.current
Text(
text = "Click Me",
textAlign = TextAlign.Center,
color = Color.Black,
modifier = Modifier.padding(16.dp).fillMaxWidth().clickable(onClick = {
Toast.makeText(context, "Thanks for clicking! I am Text", Toast.LENGTH_SHORT).show()
}, onLongClick = {
Toast.makeText(context, "Thanks for LONG click! I am Text", Toast.LENGTH_SHORT).show()
}, onDoubleClick = {
Toast.makeText(context, "Thanks for DOUBLE click! I am Text", Toast.LENGTH_SHORT).show()
})
)
}
@Composable
fun SimpleImageComponent() {
// Image is a composable that is used to display some image.
val image = imageResource(R.drawable.mindorks_cover)
Column(
modifier = Modifier.padding(16.dp)
) {
Image(image)
}
}
还可以通过Modifier设置圆角显示
Image(
image,
modifier = Modifier.fillMaxWidth().clip(shape = RoundedCornerShape(8.dp)),
contentScale = ContentScale.Fit
)
@Composable
fun AlertDialogComponent() {
val openDialog = remember { mutableStateOf(true) }
if (openDialog.value) {
AlertDialog(
onDismissRequest = { openDialog.value = false },
title = { Text(text = "Alert Dialog") },
text = { Text("Hello! I am an Alert Dialog") },
confirmButton = {
TextButton(
onClick = {
openDialog.value = false
/* Do some other action */
}
) {
Text("Confirm")
}
},
dismissButton = {
TextButton(
onClick = {
openDialog.value = false
/* Do some other action */
}
) {
Text("Dismiss")
}
},
backgroundColor = Color.Black,
contentColor = Color.White
)
}
}
Material规范中少不了AppBar的使用,TopAppBar
实现顶部AppBar,可以设置action,并为其设置icon、onClick等
@Composable
fun TopAppBarComponent() {
TopAppBar(
modifier = Modifier.padding(16.dp).fillMaxWidth(),
title = { Text("App Name") },
navigationIcon = {
IconButton(onClick = { /* doSomething() */ }) {
Icon(Icons.Filled.Menu)
}
},
actions = {
IconButton(onClick = { /* doSomething() */ }) {
Icon(Icons.Filled.Favorite)
}
IconButton(onClick = { /* doSomething() */ }) {
Icon(Icons.Filled.Favorite)
}
}
)
}
类似的,BottomAppBar
提供了底部AppBar的实现
BottomNavigation实现了NavigationBar效果,通过BottomNavigationItem
往里添加Item
@Composable
fun BottomNavigationWithLabelComponent() {
var selectedItem by remember { mutableStateOf(0) }
val items = listOf("Home", "Blogs", "Profile")
BottomNavigation(
modifier = Modifier.padding(16.dp).fillMaxWidth(),
backgroundColor = Color.Black,
contentColor = Color.Yellow
) {
items.forEachIndexed { index, item ->
BottomNavigationItem(
label = { Text(text = item) },
icon = { Icon(Icons.Filled.Favorite) },
selected = selectedItem == index,
onClick = { selectedItem = index }
)
}
}
}
如果不想显示item的label,可以设置alwaysShowLabels = false
@Composable
fun SimpleCheckboxComponent() {
val checkedState = remember { mutableStateOf(true) }
Row {
Checkbox(
checked = checkedState.value,
modifier = Modifier.padding(16.dp),
onCheckedChange = { checkedState.value = it },
)
Text(text = "Checkbox Example", modifier = Modifier.padding(16.dp))
}
}
onCheckedChange
响应事件处理
CircularProgressIndicator
、LinearProgressIndicatorCompose
分别实现了圆形和线形的ProgressBar,例如:
@Composable
fun SimpleCircularProgressComponent() {
CircularProgressIndicator(
modifier = Modifier.padding(16.dp)
)
}
progress = 0.4f
可以设置当前进度
Slider通过滑动实现数量调节,例如音量、亮度的调节等
@Composable
fun SimpleSliderComponent() {
var sliderValue by remember { mutableStateOf(0.4f) }
Slider(
value = sliderValue,
modifier = Modifier.padding(8.dp),
onValueChange = { newValue ->
sliderValue = newValue
}
)
Text(
text = "Slider value: $sliderValue",
modifier = Modifier.padding(8.dp)
)
}
还可以通过传递参数steps
,实现步进式的调节
Snackbar从底部弹出,比toast提供跟丰富的交互能力
@Composable
fun SimpleSnackbarComponent() {
Snackbar(
modifier = Modifier.padding(16.dp),
text = {
Text(text = "I'm a Simple Snackbar")
}
)
}
还可以为Snackbar添加更多action
action = {
Text(text = "OK", style = TextStyle(color = Color.Green))
}
除了上面介绍的各种组件以外,Compose还允许直接在Canvas上绘制更多自定义组件
@Composable
fun CustomViewComponent() {
Canvas(modifier = Modifier.fillMaxSize().padding(16.dp)) {
drawRect(
color = Color.Red,
// topLeft is the coordinate of top-left point
topLeft = Offset(0f, 0f),
size = Size(800f, 400f)
)
drawArc(
Color.Gray,
startAngle = 0f,
sweepAngle = 120f,
useCenter = true,
size = Size(600f, 600f),
topLeft = Offset(300f, 300f)
)
}
}
Compose提供了多种效果的动画能力,例如我们可以实现CrossFade的动画效果
@Composable
fun CrossFadeAnimation() {
val colors = listOf(Color.Red, Color.Green, Color.Blue, Color.Gray)
var current by remember { mutableStateOf(colors[0]) }
Column(modifier = Modifier.fillMaxSize()) {
Crossfade(current = current) { color ->
Box(Modifier.fillMaxSize().clickable(
onClick = {
current = colors.random()
}
).background(color))
Text(
modifier = Modifier.fillMaxSize(),
textAlign = TextAlign.Center,
text = "Click To See"
)
}
}
}
如上,当我们点击Box时,Box将会通过Crossfade的动画过渡到指定颜色。
更多详细实例,请移步 Learn Jetpack Compose for Android by example