美文网首页
Android Jetpack Compose

Android Jetpack Compose

作者: SimpleFunc | 来源:发表于2020-10-26 20:14 被阅读0次

    Android Jetpack Compose是谷歌推出的一种新的搭建UI的方式,使用Kotlin DSL的形式来组合UI组件。目前还在alpha阶段。苹果早也推出了类似的Swift UI,都是模仿前端的实现方式,目的都是UI更加轻量化和方便数据驱动。

    Compose使用感受

    Compose的使用比较简单,官方有连续的课程,还有比较完善的samples可以参考,网络上也能找到好多相关的教程,就不再重复写了。需要注意的是,Compose还在alpha阶段,API还未稳定,网上的教程好多是针对旧的API的,遇到问题还是尽量查看官方文档。下面就简单说说在使用Compose时的一些感受。

    好的地方:

    1. 相对于之前的xml方式,代码编写起来可能更加熟练,对开发可能更亲切(个人感受)
    2. 一般的UI组件使用起来更加简单,如Text:
     Text(
        "text",
        style = MaterialTheme.typography.body,
        modifier = Modifier.padding(start = 4.dp) 
    )
    

    相对于xml中声明TextView,代码量会小很多。

    1. 可能是由于目前Compose提供的组件比较少,布局实现比较单一,感觉实现比较复杂的布局比XML高效,简洁很多。而且性能也比XML高,毕竟少了xml解析的过程。
    2. 可以多个UI组件一起预览。传统方式一次只能预览一个xml布局,使用Compose之后,只要在Compose上添加@preview注解,在一个文件内都可以预览。在搭建UI时几乎可以不再需要安装到真机和模拟器上进行检查。
    3. 数据驱动UI更新更加直观和高效,只需要声明state即可,数据改变时会自动更新UI,不再需要之前的Livedata等比较复杂的机制。
    4. Theme更加强大和自由,以前在XML中声明Theme和自定义主题中某个组件的样式,是比较麻烦的,因为属性太多了。现在就比较方便了可以直接使用Material Design主题中定义好的样式,更加规范高效,而且深色主题适配更加简单。
    5. 动画API更加简单了,不再需要复杂的写法,会自动根据之前的属性生成对应的动画,如更改组件的大小,只需要在modifier中添加:
    modifier.animateContentSize(animSpec = TweenSpec(300)),
    

    就可以了,系统会自动监控这个组件的大小的变化,生成动画。

    不好的地方

    1. 由于使用的是Kotlin DSL,所以代码排版缩进会比较多,相对于一般代码结构,直观性比较差。但是还是比Flutter的好一些。
    2. 由于要实现实时预览,每次修改Compose都需要编译,如果项目比较大,编译时间很长,那体验就会很差了
    3. 组件的丰富度还比较欠缺,需要进一步完善
    4. 官方的教程,文档包含的内容有限,有很多之前的组件找不到在Compose中对应的组件,命名也发生了变化。寻找起来比较麻烦。大部门只能看官方的文档。而且对复杂的界面布局的实现没有比较完善的指导文档。
    5. jetpack组件还在推广中,Compose已经和好多AndroidX的组件冲突了,如果Compose推进的比较快,那还有必要学习使用AndroidX中的UI和管理UI组件吗? 一般的项目也不会想两套共存吧? 所以如何推广Compose还是个问题, 好处是不像iOS的Swift UI那样,和系统版本绑定。

    Compose的实现

    最初看到Compose的时候,以为就是对之前的View组件做了一次封装,然后底层再做组装,渲染处理。后来看了下源码,发现不是这样的,是重新实现了一套。如Text的具体实现是:CoreText, layout:

    Layout(
            children = if (inlineComposables.isEmpty()) {
                emptyContent()
            } else {
                { InlineChildren(text, inlineComposables) }
            },
            modifier = modifier
                .then(controller.modifiers)
                .then(
                    if (selectionRegistrar != null) {
                        Modifier.longPressDragGestureFilter(
                            longPressDragObserver(
                                state = state,
                                selectionRegistrar = selectionRegistrar
                            )
                        )
                    } else {
                        Modifier
                    }
                ),
            minIntrinsicWidthMeasureBlock = controller.minIntrinsicWidth,
            minIntrinsicHeightMeasureBlock = controller.minIntrinsicHeight,
            maxIntrinsicWidthMeasureBlock = controller.maxIntrinsicWidth,
            maxIntrinsicHeightMeasureBlock = controller.maxIntrinsicHeight,
            measureBlock = controller.measure
        ) 
    

    TextController控制Text的layout,state, selection, measure和draw.其实都是通过TextDelegate实现的

        fun layout(
            constraints: Constraints,
            layoutDirection: LayoutDirection,
            prevResult: TextLayoutResult? = null,
            respectMinConstraints: Boolean = false
        ): TextLayoutResult {
            val minWidth = if (respectMinConstraints || style.textAlign == TextAlign.Justify) {
                constraints.minWidth.toFloat()
            } else {
                0f
            }
            val widthMatters = softWrap || overflow == TextOverflow.Ellipsis
            val maxWidth = if (widthMatters && constraints.hasBoundedWidth) {
                constraints.maxWidth.toFloat()
            } else {
                Float.POSITIVE_INFINITY
            }
    
            if (prevResult != null && prevResult.canReuse(
                    text, style, maxLines, softWrap, overflow, density, layoutDirection,
                    resourceLoader, constraints
                )
            ) {
                return with(prevResult) {
                    copy(
                        layoutInput = layoutInput.copy(
                            style = style,
                            constraints = constraints
                        ),
                        size = computeLayoutSize(constraints, multiParagraph, respectMinConstraints)
                    )
                }
            }
    
            val multiParagraph = layoutText(
                minWidth,
                maxWidth,
                layoutDirection
            )
    
            val size = computeLayoutSize(constraints, multiParagraph, respectMinConstraints)
            return TextLayoutResult(
                TextLayoutInput(
                    text,
                    style,
                    placeholders,
                    maxLines,
                    softWrap,
                    overflow,
                    density,
                    layoutDirection,
                    resourceLoader,
                    constraints
                ),
                multiParagraph,
                size
            )
        }
    

    layout最终返回一个TextLayoutResult,TextLayoutResult在draw的时候使用。
    draw:

    fun paint(canvas: Canvas, textLayoutResult: TextLayoutResult) {
           TextPainter.paint(canvas, textLayoutResult)
    }
    

    最终调用了TextPainter:

     fun paint(canvas: Canvas, textLayoutResult: TextLayoutResult) {
            val needClipping = textLayoutResult.hasVisualOverflow &&
                textLayoutResult.layoutInput.overflow == TextOverflow.Clip
            if (needClipping) {
                val width = textLayoutResult.size.width.toFloat()
                val height = textLayoutResult.size.height.toFloat()
                val bounds = Rect(Offset.Zero, Size(width, height))
                canvas.save()
                canvas.clipRect(bounds)
            }
            try {
                textLayoutResult.multiParagraph.paint(
                    canvas,
                    textLayoutResult.layoutInput.style.color,
                    textLayoutResult.layoutInput.style.shadow,
                    textLayoutResult.layoutInput.style.textDecoration
                )
            } finally {
                if (needClipping) {
                    canvas.restore()
                }
            }
        }
    

    设计思想还是View的那套,但是所有组件的实现更加扁平了。不再有View或Viewgroup的继承关系,大部分组件都是直接自己直接实现layout,measure和draw。所以看起来更加简洁。

    State

    Compose的更新数据显示是通过State来实现的,而且比之前的LiveData更加简单,如:
    数据:

    val list = listOf(
        "ListItem1“, 
        "ListItem2“,
        "ListItem3“,
        "ListItem4“,
        "ListItem5“)
    
    val data by remember{ mutableStateOf(topics) }
    

    UI:

    LazyColumnFor(
         items = data,
         modifier = Modifier.weight(1f),
         contentPadding = PaddingValues(top = 8.dp))
    { text ->
           Text(text = text)
     }
    

    添加数据:

    data.value = list += listOf("ListItem6")
    

    添加之后列表会自动刷新数据和UI。不需要再相之前需要主动notify UI更新。
    其中remember{} 的表达的意思是跟字面意思一致,就是记住后面block中产生的value, 只有在UI组合时才会产生值。mutableStateOf就是产生一个SnapshotMutableState,里面的value的读和写都是被监控的。在UI组合完成之后Composer会一直监控state, 如果有值发生变化则会出发Recomposition. 进行UI更新。

    Recomposition

    官方翻译为重组,就是重新调用compose组合UI的过程,系统会根据需要使用新数据重新绘制函数发出的组件。因为UI是一直显示的,重组可能会很频繁,为了保证流畅性,官方做了很多优化,如并行处理,自动跳过不需要重组的组件和使用乐观算法优化重组。总之和之前的UI实现一样,不要在compose组合期间执行比较耗时的逻辑。

    相关文章

      网友评论

          本文标题:Android Jetpack Compose

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