Jetpack Compose入门基础教程

1,643 阅读3分钟

我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!

概述

Jetpack Compose是谷歌在2019Google I/O大会上发布的用于构建原生 Android 界面的新款工具包,并于2021年7月正式发布1.0版本;它可简化并加快 Android 上的界面开发,使用更少的代码、强大的工具和直观的 Kotlin API,快速让应用生动而精彩。

开发环境

Android Studio Arctic Fox | 2020.3.1发布之前,我们想要体验Jetpack Compose还要去下载Android studio的预览版Canary,但现在最新版本已经更新到Android Studio Bumblebee | 2021.1.1,对Jetpack Compose的支持已经非常完善了,如果你还在使用旧版本,不要犹豫,更新到最新版本一起来学习体验下Jetpack Compose相比之前开发的优点吧!

创建项目

下载安装好开发工具后,我们来尝试新建一个Jetpack Compose项目,点击开发工具左上角File-New-New Project,如下图所示,之前我们通常会选择Empty Activity来创建一个空的项目,但是仔细观察我们发现在它的左边还有一个Empty Compose Activity,我们选择这个并且点击下一步最后就会发现已经创建好一个空的Jetpack Compose项目了。

image.png

分析项目

首先我们分析下主界面MainActivity,默认继承自ComponentActivity,最终也是继承Activity,这里不去深究它;值得注意的是setContent中加载的居然xml不是布局,而是一个Composable函数,这个时候我们就大概了解了,Jetpack Compose应该是将Composable函数转换为了应用程序的UI元素;

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyComposeTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colors.background
                ) {
                    Greeting("Android")
                }
            }
        }
    }
}

@Composable
fun MyComposeTheme(darkTheme: Boolean = false, content: @Composable () -> Unit) {}

我们注意到MyComposeTheme函数中又调用了一个Surface函数,而在Surface函数中又调用了Greeting函数,这些函数有一个共同点,就是都存在@Composable注解,因此我们大概知道这个注解就是用来将普通函数声明为Composable函数的;

@Composable
fun Surface(
    modifier: Modifier = Modifier,
    ... ...
)
@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

接着我们发现还有个@Preview注解,这里大胆猜测,应该就是用来预览Composable函数生成的UI的,我们把当前页面右上角的显示模式切换为Split,就会看到默认创建的UI了,很简单,只有一个文本;

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyComposeTheme {
        Greeting("Android")
    }
}

image.png image.png

是不是发现和我们传统创建UI界面的方式完全不同,没有了XML布局文件,取而代之的是不停嵌套的Composable函数,这种使用函数声明实现UI的方式我们也可以简单的认为就是声明式UI。

基础使用

实践是检验真理的唯一标准,我们来写个简单的界面快速上手Jetpack Compose,比如我们要实现如下功能:添加一个输入框和一个悬浮添加按钮,每次点击添加按钮的时候输入框内容会被添加到一个带图片的列表中,然后点击列表又会将此条数据从列表中删除。

首先我们来回顾下初始创建的项目,MyComposeTheme函数是一个Composable函数,它继承自MaterialTheme函数,而MaterialTheme函数是用来表示主题的,有四个属性,分别是colors(颜色)、typography(排版)、shape(形状)、content(内容),因此我们可以大致知道主题的基本组成,MaterialTheme主题内置了很多Material Design组件,因此我们这里直接使用MaterialTheme

setContent {
    MaterialTheme {
        Surface()
    }
}

另外我们发现主题下方又调用了一个Surface函数,这里我们可以理解为一个容器,细心的小伙伴会发现我们之前的默认项目预览界面容器大小好像只有内容大小,但是真机运行却是占满全屏的,这是通过Surfaced函数的Modifier.fillMaxSize()属性实现的,表示铺满最大值,我们可以这段代码拷贝到预览函数中,就会发现预览界面也可以占满全屏了,另外我们自己创建一个MainBody()函数用来实现我们方开始的需求;

Surface(
    modifier = Modifier.fillMaxSize(),
    color = MaterialTheme.colors.background
) {
    MainBody()
}
@Composable
fun MainBody() {}

熟悉Material Design风格的小伙伴知道它提供了drawer、snackbar等很多炫酷的内置控件,Scaffold组件实现了基本的Material Design视觉布局结构,也可以轻松构建那些组件,所以我们这里作为根组件,topBar属性用来展示一个标题栏,而且还有一个floatingActionButton属性就是用来创建一个悬浮按钮的,刚好我们有这个需求,而TopAppBarExtendedFloatingActionButton组件是官方已经实现好的,属性也很简单,这里直接使用看下效果;

Scaffold(
    topBar = {
        TopAppBar() {Text(text = "Compose") }
    },
    floatingActionButton = {
        ExtendedFloatingActionButton(
            onClick = {},
            icon = { Icon(Icons.Filled.Add, contentDescription = "Add", tint = Color.White) },
            text = { Text(text = "添加", color = Color.White) }
        )
    },
) 

image.png image.png

接着我们来看主体部分的内容实现,分为输入框组件和一个列表组件,因为整体纵向排列,这里使用Column来表示,我们先来创建输入框组件TextField,并添加了默认提示文本和一些颜色属性,而且还在尾部添加了一个清除图标,用于快速删除内容;

TextField(
    value = "",
    onValueChange = { },
    placeholder = {
        Text(text = "Please input content")
    },
    // 尾部图标
    trailingIcon = @Composable {
         Image(imageVector = Icons.Filled.Clear, // 清除图标
    },
    colors = TextFieldDefaults.textFieldColors(
        backgroundColor = Color.White,
        placeholderColor = Color.Black,
        textColor = MaterialTheme.colors.primary,
        cursorColor = MaterialTheme.colors.primary
    ),
    modifier = Modifier
        .fillMaxWidth()
)

image.png

虽然已经成功展示了,但是当你试图输入内容时你会发现无论输入什么都不会看到,这是因为TextField中的内容并不会自动更新,需要我们自己处理,这点和原来的ExitText还是有区别的,这个时候就需要用到Compose中的状态了,mutableStateOf会创建可观察的值,当值变化时会重组读取此值的所有可组合函数,而remember用于将此值保存起来,因此我们可以利用这个特点来实现输入框内容更新,这样我们就可以正常使用了,而且我们还可以通过内容判断来决定是否显示清除按钮,并且点击按钮时清除输入的内容;

val content = remember { mutableStateOf<String>("") }
TextField(
    value = content.value,
    onValueChange = { content.value = it },
    placeholder = {
        Text(text = "Please input content")
    },
    if (content.value.isNotEmpty()) {
        Image(imageVector = Icons.Filled.Clear,
        contentDescription = null,
        modifier = Modifier.clickable {
            content.value = ""
    })
)

image.png

接下来我们来看列表的构建,虽然我们可以使用verticalScroll() 修饰符使 Column可滚动,但是大量数据会导致性能问题,熟悉RecyclerView的应该知道内部对Item做了优化,而Compose同样提供了LazyColumnLazyRow来实现列表滚动,而且还提供了items扩展函数用来快速添加元素项,为了后续动态改变集合,我们这里使用了mutableStateListOf来对列表集合进行观察;

data class Message(val title: String, val content: String)
val messages = remember { mutableStateListOf<Message>() }
LazyColumn {
    items(messages) { msg ->
        getItem(messages = messages, msg = msg)
    }
}

接着我们来看Item项的UI创建,我们打算实现如下效果,整体横向排列,然后标题和内容又纵向排列,这里使用了Row横向排列组件和Image图片组件,图片组件使用了clip(RectangleShape)来将图片裁剪为矩形,并用 contentScale属性设置了显示方式,类似于xml中的scaleType,而Spacer组件是用来控制间距的,当然你也可以使用padding来实现;

image.png

Row(
    modifier = Modifier.fillMaxWidth() .padding(10.dp).clickable {}, 
    verticalAlignment = Alignment.Top     
) {
    Image(
        painter = painterResource(id = R.mipmap.jetpack_compose),
        contentDescription = null,
        modifier = Modifier.height(60.dp).width(80.dp).clip(RectangleShape)
           .border(1.dp, MaterialTheme.colors.primary, RectangleShape),
            contentScale = ContentScale.FillBounds
    )
    Spacer(modifier = Modifier.width(10.dp))
    Column() {
        Text(text = msg.title, fontWeight = FontWeight.W500,
            fontSize = 18.sp,color = Color.Black   
        )
        Text(text = msg.content, textAlign = TextAlign.Center,
             fontSize = 16.sp, softWrap = true, maxLines = 2,
        )
    }
}

可能我们平常习惯了Item之间的分割线,这里我们可以在最外层再添加一个纵向布局,然后底部使用Divider组件来实现分割效果;

Divider(modifier = Modifier.height(0.5.dp), color = MaterialTheme.colors.primary)

最后我们来看看数据如何展示呢,还记得之前的悬浮按钮吗,有没有注意到它onClick就是用来实现点击事件的,这里我们获取到输入的内容然后添加到之前的messages可观察数据中,

onClick = {
    messages.add(
        Message(
            "标题${messages.size + 1}",
            content.value
        )
    )
},

到这里还有最后一个需求,点击Item删除当前项数据,虽然布局没有直接提供点击事件,但是我们可以借助强大的修饰器Modifier来实现,它还支持设置背景、间距、大小、阴影等可快速帮助您修饰或扩充可组合项,因为Item函数我们也是单独创建的,为了更新列表值,我们可以通过传参的方式来解决;

@Composable
fun getItem(messages: SnapshotStateList<Message>, msg: Message) {}

Modifier.clickable {
        messages.remove(msg)
    }
}

好了,到这里我们就基本完成了一开始打算实现的一个小需求,虽然整体比较简单,但是基本涉及到了布局、列表、文本、图片以及数据处理等基本组件和逻辑,下面我们来看下完成的效果图;

image.png image.png image.png

总结

以上只是Jetpack Compose的简单入门,其实还有很多有趣的玩法和高级使用,虽然和原来的命令式构建UI有很大区别,但是代码更直观简洁,而且Jetpack Compose兼容现有的所有代码,并且内置了很多组件及动画的支持,快来体验下吧!