使用Compose写UI
compose是android新推出的UI工具包,使用可组合函数以声明式来构建UI,不再使用xml布局文件
使用compose构建ui的例子:

运行效果图

既然Compose是全新的一套UI工具包,那么它是如何构建UI的,是否用到了诸如LinearLayout、RelativeLayout、Button、Text这些android view呢?
让我们来分析上图的布局
遍历view,输入如下:

没有ImageView、TextView等android view,看来compose是完全自己实现了一套布局和绘制流程。
虽说compose没有使用android的ui组件,但是如果没有硬件加速,还是会有很多子view,例如关闭compose的一个标志位,会出现很多view。如反射设置androidx.compose.ui.platform.AndroidComposeView.isRenderNodeCompatible为false,再打印view树如图:

多出了ViewLayerContainer,它包含了很多ViewLayer。后面再分析为什么会出现这么多ViewLayer。
可重组方法执行流程
Column方法,最后执行Layout方法

Image方法,最后执行Layout方法

Text方法一样最后执行Layout方法
Layout方法

执行ComposeUiNode.Constructor会创建一个LayoutNode


最后所有的方法都执行到了ComposeNode方法,这里边进行创建LayoutNode并创建子LayoutNode。基本上可以确定compose ui由LayoutNode来组成
compose ui 和android ui嵌套用法
- 在androidview嵌入compose ui
- Actvity根布局:在activity调用setContent {}方法
- 其他:在其他view嵌入ComposeView,然后调用ComposeView的setContent{}方法
- 在compose ui嵌入android view
- 在可重组函数内部调用AndroidView可重组函数来嵌入android view
compose ui结构图
- android ui和compose ui结构图

- LayoutNode的LayoutNodeWrapper和Modifer链

绘制流程
compose的ui绘制不依赖android ui控件,是完全实现了一套绘制流程。通过LayoutNode树完成绘制,LayoutNode内部通过LayoutNodeWrapper来层层绘制,每层最终由DrawModifier(Modifier子类)来进行绘制
-
绘制方法传递:从android层传递到LayoutNode,LayoutNode内部绘制结束之后继续绘制子LayoutNode
- compose从AndroidComposeView的dispatchDraw开始绘制,调用root: LayoutNode的draw方法
- LayoutNode的draw方法调用outerLayoutNodeWrapper: LayoutWrapper的draw方法
- LayoutNodeWrapper依次向后调用,LayoutNodeWrapper绘制结束后,再依次调用LayoutNode的子节点的draw方法
- LayoutNode内部是分层绘制,每层最终由DrawModifier(Modifier子类)来进行绘制
- 以上面的Demo为例,给Column底部添加颜色和文字
-
添加红色部分代码:
image.png
-
效果如图:
image.png
-
跟踪drawBehind方法
image.png
image.png
-
drawBehind方法最终创建了一个DrawBackgroundModifer(集成自DrawModifer),并把DrawBackgroundModifer通过CombinedModifier连接在modifier链上
image.png
-
之后在给LayoutNode的modifer赋值的时候,判断如果modifier是DrawModifer就用ModifiedDrawNode包装到LayoutNodeWrapper链
image.png
-
调用LayoutNode.draw方法时,从外向内执行LayoutNodeWrapper.performDraw方法,在performDraw方法执行绘制方法块
image.png
-
- 为什么关闭硬件加速后出现很多LayerView?
-
我们看LayerView的实现的OwnedLayer。它定义了缩放、透明度、位移、阴影、旋转等多种ui属性
image.png
-
再看创建layer的代码,如果是硬件加速使用RenderNodeLayer,如果没有硬件加速会使用LayerView
image.png
-
调用Modifier.graphicsLayer { }和Modifier.graphicsLayer()会添加layer
- 调用Modifier.graphicsLayer { } 在方法块里边修改ui属性
- Modifier.graphicsLayer() 在函数里边传递ui属性
-
我们看LayerView的实现的OwnedLayer。它定义了缩放、透明度、位移、阴影、旋转等多种ui属性
布局流程
compose的布局同样不使用android ui控件布局,是全新的一套布局流程。通过LayoutNode树完成布局,LayoutNode的大小和位置由LayoutNodeWrapper链来确定,最终都是通过Modifier来确定布局的
-
从AndroidComposeView触发布局流程,MeasureAndLayoutDelegate来分发布局事件
image.png
image.png
image.png
-
测量流程
-
测试最终也是从LayoutNodeWrapper层层测量来确定大小的,确定大小之后修改LayoutNodeWrapper相关的Layer大小
image.png
-
performMeasure最终调用LayoutModifier.measure来测量
-
以Column为例
-
通过modifier.requestHeight设置高度180
image.png
-
requestHeight实际会创建一个SizeModifier,并且链接到Modifier链
image.png
-
SizeModifier继承自LayoutModifer,并且实现了设置测量的功能
image.png
-
在LayoutNode遍历modifer的时候,遇到LayoutModifer后用ModifiedLayoutNode来包装LayoutModifer
image.png
-
ModifiedLayoutNode在测量时调用LayoutModifier去测量大小
-
LayoutModifer之后用自身的Constraints去继续向下层LayoutNodeWrapper测量
-
最终测量到最接近LayoutNode的InnerPlaceable(LayoutNodeWrapper子类),在这里会触发测试子LayoutNode
image.png
-
完成测量
-
-
设置位置。
- 测试完成之后会调用LayoutNode.place(或者replace)设置位置,最终执行到接口Placealble的placeAt方法
-
先调用DelegatingLayoutNodeWrapper的placeAt方法,这里会调用LayoutNodeWrapper的placeAt方法,然后再调用measureResult.placeChildren去设置下层LayoutNodeWrapper的位置
image.png
image.png
-
总结
- compose是全新开发的ui框架,不依赖android ui
- compose的ui树由LayoutNode构成
- LayoutNode其实是简单的数据对象,实际的布局、绘制等都依赖Modifier链
- 在缺少硬件加速的情况下,会存在很多ViewLayer的android view对象,他们的父类容器是ViewLayerContainer,ViewLayer都是兄弟节点
网友评论