Jetpack Compose 是Google发布的一个Android原生现代UI工具包,它完全采用Kotlin编写,可以使用Kotlin语言的全部特性,可以帮助你轻松、快速的构建高质量的Android应用程序。如果你还不了解Jetpack Compose是什么?建议你读一下我前面的2篇文章:

Android Jetpack Compose 最全上手指南

Jetpack Compose,不止是一个UI框架!

去年的Google IO 大会上,Google宣布了Jetpack Compose的面世,但是在去年11月份,它才发布第一个预览版-Developer Preview1,此后,基本保持每两周发布一个小版本,到现在,半年的时间过去了,中间发布了十多个小版本,今天,终于迎来了重大更新,Developer Preview2 发布了。

Jetpack Compose Developer Preview1发布后,开发者最关心的几个问题是,没有Compose版本的RecyclerView、Constriantlayout、动画等一系列问题。这些问题在Preview2都解决了。

当然,从Preview1 到现在发布的Preview2,变化非常大,甚至很多API都已经变了,有的属性或者类的增加或者删除。具体的变换化太多,就不在这里一一讲解,感兴趣的可以看看官方的每个小版本的更新日志。今天就带大家一起看看PreView2增加的一些重磅功能。

  • 1、Modifier
  • 2、RecyclerView
  • 3、Constriantlayout
  • 4、动画
  • 5、原生View引入Compose

好戏开场了!

1、Modifier

首先,说一下Modifier(修改器),在Preview1版本,就已经有了modifier,不过使用的地方不多,并且对于它的定位比较模糊,令人困惑,因为modifier能干的事儿,通过组合函数也能做到。但是我们发现了一件事,例如,要在Compose函数中增加padding的时候,会产生大量的嵌套,因为要给嵌套一个容器才能设置padding,因此,现在将很多功能都移动到了Modifier,它现在使用非常广泛,可以修饰一个元素、一个布局或者一些其他行为。如何使用Modifier?先来看一个例子:

首先,我们写一个Compose函数(即Compose组件),展示一张图片

@Composablefun Greeting() {    val (shape,setShape) = state<Shape> { CircleShape }    Image(asset = imageResource(R.drawable.androidstudio),        contentScale = ContentScale.Crop )}

图片显示的是原来的尺寸,然后给图片指定一个大小,比如:256dp,此时就需要使用Modifier了。

@Composablefun Greeting() {    val (shape,setShape) = state<Shape> { CircleShape }    Image(asset = imageResource(R.drawable.androidstudio),        contentScale = ContentScale.Crop,     modifier = Modifier.size(256.dp))}

修改后如下,宽高都为256dp。

modifier中有很多可以配的参数,比如,增加一个padding,将图片裁剪成一个圆形

@Composablefun 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,代码如下:

@Composablefun 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个:

@Composablefun 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中增加如下代码:

@Composablefun Greeting() {    val (shape,setShape) = state { 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,效果就是点击图片,形状在这两种效果之间来回切换。

效果如下:

2. Jetpack Compose 中的RecyclerView

RecyclerView是我们Android开发中用来展示大数据列表的常用组件,它能帮助我们回收复用视图,有很好的性能体验。在Jetpack Developer PreView1 刚出来的时候,我就在官网或者代码库中找这个组件。很遗憾翻遍了所有资料都每找到,是确实没有,最终只找到了一个叫做VerticalScroller的组件你。它可以用来展示列表,但是它不是RecyclerView,它类似我们的ScrollView,也就是说,展示少量数据的列表是可以的,因为它没有复用机制,展示大列表时,内存堪忧,会OOM。

但是在这次的Preview2中,RecyclerView终于被盼来了,组件名字叫做:AdapterList,它就对应我们原生Android开发的RecyclerView。以前我们要写一个列表是非常复杂的,用写xml,Adapter,ViewHolder等,最终还要在Activity/Fragment初始化和绑定数据,非常麻烦。Jetpack Compose中的列表使用则是非常简单,简单到令人发指。来看一下我们如何展示一个列表:

@Composablefun 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,看一下效果:

3. Constriantlayout

Constriantlayout是一个功能非常强大的布局,也是现在Android开发中最受欢迎的布局之一,当Jetpack Compose Preview1版本才出来的时候,很多开发者都有一个疑问,Compose 中该如何使用Constriantlayout呢?它将如何运作,这确实是个有意思的问题。因为在Jetpack Compose中,所有的组件都是组合函数,获取不到View饮用,如何约束彼此之间的关系确实是一个难题。好在现在这个难题解决了,下面通过几个小例子一起来看看Compose中的Constriantlayout使用。

如下图所示,有两个View,A和B,ViewB在ViewA的右边,顶部和ViewA的底部对齐,如何使用Constriantlayout 描述它们的位置关系?

代码:

@Composablefun 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 它的位置可能有两种情况:

  • 1、ViewC 的左边与ViewA的右边对齐
  • 2、View C的左边与ViewB的右边对齐

该怎么写代码?先定一个一个Boolean 变量叫hasFlag(随便其的名,它的值根据你的业务逻辑某些情况是true,某些情况是false)

 val hasFlag = true // 它的值根据你的业务逻辑某些情况是true,某些情况是false  tag("ViewC").apply {            // 根据判断条件改变,约束也改变            left constrainTo (if(hasFlag) viewA else viewB).right            bottom constrainTo viewB.top        }

完整代码如下:

@Composablefun 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相同,有兴趣的可以去试试。

4. 动画

Jetpack Compose对动画的支持也是开发者非常关心的一个问题,这一小节就看看Compose中,动画的使用,还是来看一个小例子,先看效果图:

如上,一个简单的属性动画,图片有选中/未选中两种状态,由未选中->选中时,有一个正方形->圆形的动画,并且伴随着alpha动画。

代码如下:

@Composablefun 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, 你细品,更多关于动画的使用方式这里不展开了,有兴趣的同学下来自己动手试试。

4. 与原生View 的兼容

一门新的语言,一个新的框架,考虑兼容是很有必要的,就像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 来加载:

@Composablefun 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 herefun AndroidView(@LayoutRes resId: Int, postInflationCallback: (View) -> Unit = { _ -> }) {    AndroidViewHolder(        postInflationCallback = postInflationCallback,        resId = resId    )}

接受一个布局文件资源id,和一个回调postInflationCallback,当View被inflate出来后,会调用这个回调,然后你就可以在回调中使用它了。

但是,注意: 回调通常是在主线程被调用。

5.总结

总的来说,这次Developer PreView2 更新比较多,并且很多API发生了变化,增加了一些关键的组件如AdapterList,ConstraintLayout动画组件等,使用方式也与Preview1有很多不同。可以来看一下Google关于Jetpack Compose 上的时间表:

  • 2019.5 宣布Jetpack Compose
  • 2019.11 第一个 Developer Preview
  • 2020.6 第二个 Developer Preview
  • 2020 夏天将发布Alpha版本
  • 2021 将发布release 1.0版本

但是,要说的是,现在很多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

文章首发于公众号:「 技术最TOP 」,每天都有干货文章持续更新,可以微信搜索「 技术最TOP 」第一时间阅读,回复【思维导图】【面试】【简历】有我准备一些Android进阶路线、面试指导和简历模板送给你

更多相关文章

  1. App Widget ————android 新一代移动操作系统的特征
  2. Android架构组件—LiveData
  3. Android应用的自动升级、更新模块的实现(zz)
  4. 完美获取Android状态栏高度
  5. android 页面初始化时让组件得不到焦点
  6. Android的Broadcast Receicer解析
  7. Android安卓布局简介
  8. android使用Glide加载RelativeLayout、LinearLayout等背景图片
  9. Android(安卓)开发四大天王 四大组件 (很简洁,很明晰)

随机推荐

  1. Android开发者e周报 第6期
  2. Android主题与样式
  3. 一款APP设计的从0到1之:Android设计规范篇
  4. Android,开源还是封闭?
  5. 10款最佳Android快速应用程序切换
  6. Android视频开发浅析
  7. Android中文合集 最终版
  8. 安卓巴士总结了近百个Android优秀开源项
  9. android的原理,为什么我们不需要手动关闭
  10. Android(安卓)Build 系统详解