Jetpack Compose 是Google发布的一个Android原生现代UI工具包,它完全采用Kotlin编写,可以使用Kotlin语言的全部特性,可以帮助你轻松、快速的构建高质量的Android应用程序。如果你还不了解Jetpack Compose是什么?建议你读一下我前面的2篇文章:
Android Jetpack Compose 最全上手指南
去年的Google IO 大会上,Google宣布了Jetpack Compose的面世,但是在去年11月份,它才发布第一个预览版-Developer Preview1,此后,基本保持每两周发布一个小版本,到现在,半年的时间过去了,中间发布了十多个小版本,今天,终于迎来了重大更新,Developer Preview2 发布了。
Jetpack Compose Developer Preview1发布后,开发者最关心的几个问题是,没有Compose版本的RecyclerView、Constriantlayout、动画等一系列问题。这些问题在Preview2都解决了。
当然,从Preview1 到现在发布的Preview2,变化非常大,甚至很多API都已经变了,有的属性或者类的增加或者删除。具体的变换化太多,就不在这里一一讲解,感兴趣的可以看看官方的每个小版本的更新日志。今天就带大家一起看看PreView2增加的一些重磅功能。
好戏开场了!
首先,说一下Modifier(修改器),在Preview1版本,就已经有了modifier,不过使用的地方不多,并且对于它的定位比较模糊,令人困惑,因为modifier能干的事儿,通过组合函数也能做到。但是我们发现了一件事,例如,要在Compose函数中增加padding的时候,会产生大量的嵌套,因为要给嵌套一个容器才能设置padding,因此,现在将很多功能都移动到了Modifier,它现在使用非常广泛,可以修饰一个元素、一个布局或者一些其他行为。如何使用Modifier?先来看一个例子:
首先,我们写一个Compose函数(即Compose组件),展示一张图片
@Composable
fun Greeting() {
val (shape,setShape) = state<Shape> { CircleShape }
Image(asset = imageResource(R.drawable.androidstudio),
contentScale = ContentScale.Crop )
}
图片显示的是原来的尺寸,然后给图片指定一个大小,比如:256dp,此时就需要使用Modifier了。
@Composable
fun Greeting() {
val (shape,setShape) = state<Shape> { CircleShape }
Image(asset = imageResource(R.drawable.androidstudio),
contentScale = ContentScale.Crop,
modifier = Modifier.size(256.dp))
}
修改后如下,宽高都为256dp。
modifier中有很多可以配的参数,比如,增加一个padding
,将图片裁剪成一个圆形
。
@Composable
fun Greeting() {
val (shape,setShape) = state<Shape> { CircleShape }
Image(asset = imageResource(R.drawable.androidstudio),
contentScale = ContentScale.Crop,
modifier = Modifier.size(256.dp)
.padding(16.dp)
.drawShadow(8.dp,shape = shape)
)
}
效果就成了这样:
还可以再圆形头像加一个border
,代码如下:
@Composable
fun Greeting() {
val (shape,setShape) = state<Shape> { CircleShape }
Image(asset = imageResource(R.drawable.androidstudio),
contentScale = ContentScale.Crop,
modifier = Modifier.size(256.dp)
.padding(16.dp)
.drawShadow(8.dp,shape = shape)
.drawBorder(6.dp,MaterialTheme.colors.primary,shape = shape)
)
}
效果如下:
还可以同时添加多个border,比如我再增加2个:
@Composable
fun Greeting() {
val (shape,setShape) = state<Shape> { CircleShape }
Image(asset = imageResource(R.drawable.androidstudio),
contentScale = ContentScale.Crop,
modifier = Modifier.size(256.dp)
.padding(16.dp)
.drawShadow(8.dp,shape = shape)
.drawBorder(6.dp,MaterialTheme.colors.primary,shape = shape)
.drawBorder(12.dp,MaterialTheme.colors.secondary,shape = shape)
.drawBorder(18.dp,MaterialTheme.colors.background,shape = shape)
)
}
效果就成这样了:
设置点击事件也是再modifier中,比如我们要在点击这个图片后,改变形状,以前的View可麻烦了,但是Jetpack compose 却非常简单,modifier中增加如下代码:
@Composable
fun Greeting() {
val (shape,setShape) = state<Shape> { CircleShape }
Image(asset = imageResource(R.drawable.androidstudio),
contentScale = ContentScale.Crop,
modifier = Modifier.size(256.dp)
.padding(16.dp)
.drawShadow(8.dp,shape = shape)
.drawBorder(6.dp,MaterialTheme.colors.primary,shape = shape)
.drawBorder(12.dp,MaterialTheme.colors.secondary,shape = shape)
.drawBorder(18.dp,MaterialTheme.colors.background,shape = shape)
.clickable { // 点击事件
setShape(
if(shape == CircleShape)
CutCornerShape(topLeft = 32.dp,bottomRight = 32.dp)
else
CircleShape
)
}
)
}
上面的代码中,我们还增加了判断,如果当前shape是CircleShape
,我们就改变形状,否则就设置为CircleShape
,效果就是点击图片,形状在这两种效果之间来回切换。
效果如下:
RecyclerView是我们Android开发中用来展示大数据列表的常用组件,它能帮助我们回收复用视图,有很好的性能体验。在Jetpack Developer PreView1 刚出来的时候,我就在官网或者代码库中找这个组件。很遗憾翻遍了所有资料都每找到,是确实没有,最终只找到了一个叫做VerticalScroller
的组件你。它可以用来展示列表,但是它不是RecyclerView,它类似我们的ScrollView,也就是说,展示少量数据的列表是可以的,因为它没有复用机制,展示大列表时,内存堪忧,会OOM。
但是在这次的Preview2中,RecyclerView终于被盼来了,组件名字叫做:AdapterList
,它就对应我们原生Android开发的RecyclerView。以前我们要写一个列表是非常复杂的,用写xml,Adapter,ViewHolder等,最终还要在Activity/Fragment初始化和绑定数据,非常麻烦。Jetpack Compose中的列表使用则是非常简单,简单到令人发指。来看一下我们如何展示一个列表:
@Composable
fun generateList(context: Context) {
val list = mutableListOf<String>()
//准备数据
for (i in 1..100) {
list.add(i.toString())
}
AdapterList(data = list) {
Card(
shape = MaterialTheme.shapes.medium,
modifier = Modifier
.preferredSize(context.resources.displayMetrics.widthPixels.dp, 60.dp)
.padding(10.dp)
) {
Box(gravity = ContentGravity.Center) {
ProvideEmphasis(EmphasisAmbient.current.medium) {
Text(
text = it,
style = MaterialTheme.typography.body2
)
}
}
}
Spacer(modifier = Modifier.preferredHeight(10.dp))
}
}
看到了没,就是这样几行代码,我们的列表就完成了,解释一下代码:最开始的准备数据没啥说的,向list中添加了100个数据,然后将数据源传给AdapterList
,列表的每一个Item是一个卡片,用的是Card组件,卡片里展示了一个Text文本,最后的Spacer
用来设置item之间的间距,相当于ItemDecoration
,看一下效果:
Constriantlayout
是一个功能非常强大的布局,也是现在Android开发中最受欢迎的布局之一,当Jetpack Compose Preview1版本才出来的时候,很多开发者都有一个疑问,Compose 中该如何使用Constriantlayout
呢?它将如何运作,这确实是个有意思的问题。因为在Jetpack Compose中,所有的组件都是组合函数,获取不到View饮用,如何约束彼此之间的关系确实是一个难题。好在现在这个难题解决了,下面通过几个小例子一起来看看Compose中的Constriantlayout
使用。
如下图所示,有两个View,A和B,ViewB在ViewA的右边,顶部和ViewA的底部对齐,如何使用Constriantlayout 描述它们的位置关系?
代码:
@Composable
fun GreetConstraintLayout(context: Context) {
ConstraintLayout(constraintSet = ConstraintSet {
val viewA = tag("ViewTagA").apply {
left constrainTo parent.left
centerVertically()
}
val viewB = tag("ViewTagB").apply {
left constrainTo viewA.right
centerVertically()
top constrainTo viewA.bottom
}
}, modifier = Modifier.preferredSize(context.screenWidth().dp,400.dp).drawBackground(Color.LightGray)) {
Box(
modifier = Modifier.tag("ViewTagA").preferredSize(100.dp, 100.dp),
backgroundColor = Color.Blue,
gravity = ContentGravity.Center
) {
Text(text = "A")
}
Box(
modifier = Modifier.tag("ViewTagB").preferredSize(100.dp, 100.dp),
backgroundColor = Color.Green,
gravity = ContentGravity.Center
) {
Text(text = "B")
}
}
}
解释一下上面的代码:在ConstraintSet
中来定义约束,使用Tag来创建一个约束,后面我们就可以通过这个tag来使用我们定义的约束,返回的是一个ConstrainedLayoutReference
,ViewA的左边与父组件的左边对齐,垂直居中。ViewB的左边与ViewA的右边对齐,top与ViewA的底部对齐。也垂直居中。
比如ViewB中就是使用ViewA来作为约束条件了。
后面使用的时候,直接用Modifier.tag()
应用约束到组件上。
这还不是最牛逼,还有一个强大的功能是可以在布局约束中添加逻辑,比如:我有一个ViewC 它的位置可能有两种情况:
该怎么写代码?先定一个一个Boolean 变量叫hasFlag
(随便其的名,它的值根据你的业务逻辑某些情况是true,某些情况是false)
val hasFlag = true // 它的值根据你的业务逻辑某些情况是true,某些情况是false
tag("ViewC").apply {
// 根据判断条件改变,约束也改变
left constrainTo (if(hasFlag) viewA else viewB).right
bottom constrainTo viewB.top
}
完整代码如下:
@Composable
fun GreetConstraintLayout(context: Context) {
ConstraintLayout(constraintSet = ConstraintSet {
val hasFlag = true // 它的值根据你的业务逻辑某些情况是true,某些情况是false
val viewA = tag("ViewTagA").apply {
left constrainTo parent.left
centerVertically()
}
val viewB = tag("ViewTagB").apply {
left constrainTo viewA.right
centerVertically()
top constrainTo viewA.bottom
}
tag("ViewC").apply {
// 根据判断条件改变,约束也改变
left constrainTo (if(hasFlag) viewA else viewB).right
bottom constrainTo viewB.top
}
}, modifier = Modifier.preferredSize(context.screenWidth().dp,400.dp).drawBackground(Color.LightGray)) {
Box(
modifier = Modifier.tag("ViewTagA").preferredSize(100.dp, 100.dp),
backgroundColor = Color.Blue,
gravity = ContentGravity.Center
) {
Text(text = "A")
}
Box(
modifier = Modifier.tag("ViewTagB").preferredSize(100.dp, 100.dp),
backgroundColor = Color.Green,
gravity = ContentGravity.Center
) {
Text(text = "B")
}
Box(
modifier = Modifier.tag("ViewC").preferredSize(100.dp, 100.dp),
backgroundColor = Color.Red,
gravity = ContentGravity.Center
) {
Text(text = "C")
}
}
}
hasFlag=true
效果如下:
hasFlag=false
效果如下:
其他的一些约束布局属性同现在我们使用的ConstraintLayout
相同,有兴趣的可以去试试。
Jetpack Compose对动画的支持也是开发者非常关心的一个问题,这一小节就看看Compose中,动画的使用,还是来看一个小例子,先看效果图:
如上,一个简单的属性动画,图片有选中/未选中
两种状态,由未选中->选中
时,有一个正方形->圆形
的动画,并且伴随着alpha
动画。
代码如下:
@Composable
fun GreetAnimate(){
//
val (selected,onValueChange) = state { false }
// radius 变化
val radius = animate(if(selected) 100.dp else 8.dp)
// alpha 动画
val selectAlpha = animate(if(selected) 0.4f else 1.0f)
Surface(shape = RoundedCornerShape(
topLeft = radius,
topRight = radius,
bottomRight = radius,
bottomLeft = radius
)) {
Toggleable(
value = selected,
onValueChange = onValueChange,
modifier = Modifier.ripple()
) {
Image(
asset = imageResource(R.drawable.androidstudio),
modifier = Modifier.preferredSize(200.dp,200.dp),
contentScale = ContentScale.Crop,
alpha = selectAlpha
)
}
}
}
动画使用animate
Compose函数来完成,只需要为它提供不同的target的值,它就能帮你完成之间的变化,一旦动画创建,它就和普通的Compose函数是一样的。
注意一点 :animate
创建的动画是不能被取消的,要创建可以被取消的动画可以使用animatedValue
。还有其他两个相似动画函数:animatedFloat
,animatedColor
啥?你说看起来有点熟悉?那可不是嘛,ObjectAnimator
,ValueAnimator
, 你细品,更多关于动画的使用方式这里不展开了,有兴趣的同学下来自己动手试试。
一门新的语言,一个新的框架,考虑兼容是很有必要的,就像Kotlin那样,我们使用Kotlin不必一下子重写整个项目,你可以添加一个新的类,一个新的模块中使用Kotlin,因为它们与Java 完全相互调用。
Jetpack Compose 借鉴了经验,我们要使用Jetpack Compose,也可以慢慢来,以前的代码不用动,在你的新模块中一点一点的添加,这就涉及到与原来的View的兼容,在Compose中,可以使用AndroidView来兼容以前的Views。
比如我的Jetpack Compose 中要使用到Webview,而它本身也没有提供,该如何是好?别担心,用原来的就行。
首先,创建一个xml文件webview.xml
,里面添加Webview 布局:
<?xml version="1.0" encoding="utf-8"?>
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</WebView>
然后写一个compose 函数,使用AndroidView 来加载:
@Composable
fun androidView(){
AndroidView(R.layout.webview){ view ->
val webView = view as WebView
webView.settings.javaScriptEnabled =true
webView.settings.allowFileAccess = true
webView.settings.allowContentAccess = true
webView.loadUrl("https://juejin.im/")
}
}
加载了一个原生的Webview,然后在webview中加载了掘金的网址,效果如下:
看一下AndroidView函数签名:
@Composable
// TODO(popam): support modifiers here
fun AndroidView(@LayoutRes resId: Int, postInflationCallback: (View) -> Unit = { _ -> }) {
AndroidViewHolder(
postInflationCallback = postInflationCallback,
resId = resId
)
}
接受一个布局文件资源id,和一个回调postInflationCallback
,当View被inflate出来后,会调用这个回调,然后你就可以在回调中使用它了。
但是,注意: 回调通常是在主线程被调用。
总的来说,这次Developer PreView2 更新比较多,并且很多API发生了变化,增加了一些关键的组件如AdapterList
,ConstraintLayout
,动画组件
等,使用方式也与Preview1有很多不同。可以来看一下Google关于Jetpack Compose 上的时间表:
但是,要说的是,现在很多API还不是最终版本,可以看到,每一个打版本的变化还是蛮大的,现在仍然不能用在商用项目上。但是就jetpack Compose 本身来说,个人还是比较期待的,从上面的时间表就可以看到,大概明年就能出第一个release版本,敬请期待吧!
对了,最新版本的Jetpack Compose 需要Android Studio 4.2以上版本才能使用,想要体验的同学先安卓Android Studio 4.2 Canary 版本。去官网下载!
小版本日志列表请看:https://developer.android.com/jetpack/androidx/releases/ui?hl=ru
youtobe视频介绍请看:https://www.youtube.com/watch?v=U5BwfqBpiWU
作者:依然范特稀西