Jetpack Compose 重磅更新!增加RecyclerV

作者: 依然范特稀西 | 来源:发表于2020-06-14 22:09 被阅读0次
    maxresdefault.jpg

    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组件),展示一张图片

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

    图片显示的是原来的尺寸,然后给图片指定一个大小,比如: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。

    image

    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)
            )
    }
    

    效果就成了这样:

    image

    还可以再圆形头像加一个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)
            )
    }
    

    效果如下:

    image

    还可以同时添加多个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)
            )
    }
    

    效果就成这样了:

    image

    设置点击事件也是再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,效果就是点击图片,形状在这两种效果之间来回切换。

    效果如下:

    image

    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中的列表使用则是非常简单,简单到令人发指。来看一下我们如何展示一个列表:

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

    image

    3. Constriantlayout

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

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

    image

    代码:

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

    • 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
            }
    
    

    完整代码如下:

    @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 效果如下:

    image

    hasFlag=false 效果如下:

    image

    其他的一些约束布局属性同现在我们使用的ConstraintLayout相同,有兴趣的可以去试试。

    4. 动画

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

    image

    如上,一个简单的属性动画,图片有选中/未选中两种状态,由未选中->选中时,有一个正方形->圆形的动画,并且伴随着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, 你细品,更多关于动画的使用方式这里不展开了,有兴趣的同学下来自己动手试试。

    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 来加载:

    @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中加载了掘金的网址,效果如下:

    image

    看一下AndroidView函数签名:

    @Composable
    // TODO(popam): support modifiers here
    fun 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版本
    image

    但是,要说的是,现在很多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进阶路线、面试指导和简历模板送给你

    image

    相关文章

      网友评论

        本文标题:Jetpack Compose 重磅更新!增加RecyclerV

        本文链接:https://www.haomeiwen.com/subject/ffzfxktx.html