Android Jetpack Compose

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的具体实现是CoreTextCoreText的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最终返回一个TextLayoutResultTextLayoutResult在draw的时候使用:

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

最终调用了TextPainterTextPainter的paint方法:

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

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)