Jetpack Compose是什么
在开始使用Jetpack Compose
之前,我们先来了解一下Jetpack Compose
是什么。
Jetpack Compose
是Google在2019 I/O大会上公布开源的一个非捆绑工具包。
根据官方文档的描述,Jetpack Compose
是用于构建原生Android UI的现代化工具包。它能使用更少的代码(相比XML)完成布局、有更强大的工具支持,并且能够直接使用Kotlin来进行编写。
它基于声明式编程模型,因此你可以使用简单的代码来描述UI的样式,然后Compose
负责其他的工作——当应用的状态发生变化的时候,你的UI也会随之更新。
由于它是基于Kotlin进行构建的,因此可以与Java部分的代码进行互操作,并且可以直接访问所有的Android功能和其他的Jetpack接口。它能够与现有的UI工具包兼容,因此你可以混合使用新旧视图,并且Compose
包含了Material风格和动画相关的交互设计。
使用Jetpack Compose
为了使用Jetpack Compose
工具包,你需要下载最新的Android Studio 预览版(实际此处需要使用Android Studio 4.0的Canary版本)。这样你在使用Jetpack Compose
进行开发时,就可以体验到最新的智能编辑器相关功能,例如新的模板和所见即所得的预览功能。
具体的安装及初始化见:https://developer.android.com/jetpack/compose/setup
Composable
方法
Jetpack Compose工具包是围绕着composable函数来构建的。这些函数可以让你通过描述应用的形状和所依赖的数据来定义应用的UI,而不需要关注UI具体的构建过程。而要创建composable函数,只需要为函数添加@Composable
注解。
添加一个文本(Text)元素
在开始之前,请按照Jetpack Compose setup instructions中的说明,使用Empty Compose Activity模板创建一个新的应用。然后为你的空白Activity添加一个文本元素。代码如下:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// 通过Text()函数添加一个文本元素
Text("Hello world!")
}
}
}
这里的setContent
代码块定义了Activity的布局。我们使用了composable函数取代了XML布局文件。Jetpack Compose使用自定义的Kotlin编译插件将composable函数转换为了应用程序的UI元素。例如,Text()
函数由Compose UI库定义,你可以通过调用该函数在应用中申明一个文本元素。
定义一个composable函数
composable函数只能被composable函数调用。要声明一个composable函数,需要为函数添加@Composable
注解。示例代码如下:
// 此处添加了@Composable注解
// 向函数中传递了一个name参数
@Composable
fun Greeting(name: String) {
// 通过Text()函数添加一个文本元素
Text(text = "Hello $name!")
}
此时onCreate
中的代码修改如下:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Greeting("Android")
}
}
在Android Studio预览composable函数
从Android Studio 4.0 Canary 1开始,你可以在IDE中预览composable函数,而不需要再将应用安装到真机或者模拟器上。
其主要的限制是,用于预览的composable函数不能带有任何参数。因此,你不能直接预览之前写的Greeting()
函数,而是要另外一个一个PreviewGreeting()
函数,在该函数中调用Greeting()
,并传入需要的参数。
另外,你还需要为PreviewGreeting()
函数添加@Preview
注解。
代码如下:
@Preview
@Composable
fun PreviewGreeting() {
Greeting("Android")
}
然后重新编译你的工程。由于PreviewGreeting()
函数没有被应用调用,应用本身没有发生变化,而是在Android Studio增加了一个预览窗口用于显示被@Preview
注解的composable函数的效果。你可以点击顶部的刷新按钮来更新显示的效果。
布局
在使用Jetpack Compose构建的应用中,UI元素是有层级关系的。你可以在一个composable函数中调用另一个composable函数来构建复杂的UI效果。
从定义一些文本元素开始
回头看你的Activity,并定义新的NewsStory()
函数来替代Greeting()
函数。然后,在后续的修改中你只需要修改NewsStory()
函数,而不需要再对Activity中的代码进行任何修改。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NewsStory()
}
}
}
@Composable
fun NewsStory() {
Text("A day in Shark Fin Cove")
Text("Davenport, California")
Text("December 2018")
}
@Preview
@Composable
fun DefaultPreview() {
NewsStory()
}
定义不被应用本身调用的预览函数是一种最佳实践。专用的预览函数可以提升应用性能,并且能够更轻松的实现同时预览多个函数。这里我们需要定义一个仅调用了NewsStory()
函数的预览函数,在之后你只需要修改NewsStory()
函数即可预览到所有的修改。
上面那段代码在布局中创建了3个文本元素。但是我们没有指定排列方式,因此这3个元素都重叠在顶部,导致文本重合不可阅读。
使用Column函数
使用Column()
函数可以垂直堆放元素。在NewsStory()
函数中添加Column()
如下:
@Composable
fun NewsStory() {
Column {
Text("A day in Shark Fin Cove")
Text("Davenport, California")
Text("December 2018")
}
}
默认会将所有的子元素从左上角开始垂直堆放,并且没有间距。
为Column添加样式
在调用Column()
函数时,可以通过传入参数来控制列的大小和位置,以及子元素的排列方式。示例:
@Composable
fun NewsStory() {
Column(
crossAxisSize = LayoutSize.Expand,
modifier=Spacing(16.dp)
) {
Text("A day in Shark Fin Cove")
Text("Davenport, California")
Text("December 2018")
}
}
传入的两个参数说明如下:
-
crossAxisSize
:用来指定Column
在横轴(水平轴)方向的大小。将crossAxisSize
设置为LayoutSize.Expand
可以让其宽度填充父级允许的最大宽度。 -
modifier
:用来对一些杂项进行设置。在这里,使用Spacing()
指定了边距,使其与其父级保持一定距离。
在使用Column()
函数时,其横轴的默认宽度为LayoutSize.Wrap
,此处修改为使用LayoutSize.Expand
之后并没有看出任何变化。但是在Android Studio中进行预览时会能够看出明显的变化。
使用LayoutSize.Wrap
时:
使用LayoutSize.Expand
时:
PS:
mainAxis和crossAxis分别表示一个布局的主轴和交叉轴。在针对Row时,主轴就是其横轴,交叉轴则为纵轴。在针对Column时,主轴是其纵轴,交叉轴则为其横轴。
定义与Flutter中的主轴、交叉轴相同。
添加图片
这里我们使用Resource Manager添加一张名为header
的图片。
修改NewsStory()
函数。这里需要使用应用的上下文对象(Context
)来获取图片,并且调用DrawImage()
函数将图像添加到应用中。
@Composable
fun NewsStory() {
val image = +imageResource(R.drawable.header)
Column(
crossAxisSize = LayoutSize.Expand,
modifier=Spacing(16.dp)
) {
DrawImage(image)
Text("A day in Shark Fin Cove")
Text("Davenport, California")
Text("December 2018")
}
}
如上图所示,图片被添加到了布局中,但是比例显示不正确,它填充了整个布局并且被文本元素覆盖。
要想为图像添加样式,需要将它放到一个Contaienr
中,Container
一般用来对UI元素添加大小和排列限制。
这里就可以通过对Container
添加限制而对图像的样式进行调整。
@Composable
fun NewsStory() {
val image = +imageResource(R.drawable.header)
Column(
crossAxisSize = LayoutSize.Expand,
modifier=Spacing(16.dp)
) {
// 通过Container对图片添加样式
Container(expanded = true, height = 180.dp) {
DrawImage(image)
}
// 添加一个分隔线
HeightSpacer(16.dp)
Text("A day in Shark Fin Cove")
Text("Davenport, California")
Text("December 2018")
}
}
-
expanded
:默认值为false
,用来指定Container
的大小(用来限定其子元素的大小)。通过将expanded
的值设置为true
,可以让Container
尽可能的填充父布局。 -
height
:用来指定Container
的高度,其优先级大于expanded
的设定,故此处高度最终被限定为180DP。但是其宽度由于没有添加精确高度,expanded
仍然生效。
使用材料设计语言
Compose的出现本身就是为了适应材料设计语言的规则。它的很多UI组件本身就实现了材料设计,可以在布局时方便的实现材料设计的效果。
使用shape
材料设计语言的其中一个支柱就是Shape
。在这里我们可以使用Clip()
函数来实现图片的圆角。
而Shape
本身是不可见的,但是图像本身会被裁剪为设定的形状。代码如下:
@Composable
@Composable
fun NewsStory() {
val image = +imageResource(R.drawable.header)
Column(
crossAxisSize = LayoutSize.Expand,
modifier = Spacing(16.dp)
) {
// 通过Container对图片添加样式
Container(expanded = true, height = 180.dp) {
// 使用Clip,指定一个Shape,为图像添加8dp的圆角
Clip(shape = RoundedCornerShape(8.dp)) {
DrawImage(image)
}
}
// 添加一个分隔线
HeightSpacer(16.dp)
Text("A day in Shark Fin Cove")
Text("Davenport, California")
Text("December 2018")
}
}
为文本元素添加样式
通过使用Compose,我们可以更轻松的使用材料设计语言。这里为创建的组件添加MaterialTheme
,并且为文本元素分别添加样式:
@Composable
fun NewsStory() {
val image = +imageResource(R.drawable.header)
MaterialTheme() {
Column(
crossAxisSize = LayoutSize.Expand,
modifier = Spacing(16.dp)
) {
// 通过Container对图片添加样式
Container(expanded = true, height = 180.dp) {
// 使用Clip,指定一个Shape,为图像添加8dp的圆角
Clip(shape = RoundedCornerShape(8.dp)) {
DrawImage(image)
}
}
// 添加一个分隔线
HeightSpacer(16.dp)
Text("A day in Shark Fin Cove",
style = +themeTextStyle { h6 })
Text("Davenport, California",
style = +themeTextStyle { body2 })
Text("December 2018",
style = +themeTextStyle { body2 })
}
}
}
这里的文本显示使用了材料设计的一些默认色值。要想强化某些文本的显示效果,就需要改变其不透明度:
Text("A day in Shark Fin Cove",
style = (+themeTextStyle { h6 }).withOpacity(0.87f))
Text("Davenport, California",
style = (+themeTextStyle { body2 }).withOpacity(0.87f))
Text("December 2018",
style = (+themeTextStyle { body2 }).withOpacity(0.6f))
在以上示例中,文本都比较短。但是在一些情况下文本可能会很长,我们又不想它破坏应用的整体显示效果。这时就需要对文本添加限制。首先看没有添加限制的效果:
Text("A day wandering through the sandhills in Shark " +
"Fin Cove, and a few of the sights I saw",
style = (+themeTextStyle { h6 }).withOpacity(0.87f))
Text("Davenport, California",
style = (+themeTextStyle { body2 }).withOpacity(0.87f))
Text("December 2018",
style = (+themeTextStyle { body2 }).withOpacity(0.6f))
然后我们设定文本最多显示两行。在文本长度不足两行时,这个设定不会有任何影响,但是当文本超出两行的长度时就会被截断。
// 设定最多显示两行,并且超出时末尾显示省略号
Text("A day wandering through the sandhills in Shark " +
"Fin Cove, and a few of the sights I saw",
maxLines = 2, overflow = TextOverflow.Ellipsis,
style = (+themeTextStyle { h6 }).withOpacity(0.87f))
Text("Davenport, California",
style = (+themeTextStyle { body2 }).withOpacity(0.87f))
Text("December 2018",
style = (+themeTextStyle { body2 }).withOpacity(0.6f))
官方文档地址:https://developer.android.com/jetpack/compose/tutorial