美文网首页JetPack
一文了解Compose

一文了解Compose

作者: 鹧鸪晏 | 来源:发表于2022-03-10 10:31 被阅读0次

    一文了解Compose

    简介

    Jetpack Compose 是一个适用于 Android 的新式声明性界面工具包。阅读官方介绍可以了解到,Compose 大概是这么个东西:

    1. Compose 是一个声明性界面框架,使用更少的代码、强大的工具和直观的 Kotlin API。
    2. 抛弃了原有安卓view的体系,完全重新实现了一套新的ui体系
    3. 使用可组合函数来替换view构建UI界面,只允许一次测量,避免了布局嵌套多次测量问题,从根本上解决了布局层级对布局性能的影响。

    看完介绍后第一反应便有了以下几个疑问。

    1. Compose完全摒弃了View吗?ComposeUI结构是什么样?
    2. 可组合函数与View有什么不同?函数最终有没有转换成了view
    3. ComposeUI是如何解多次测量问题问题的?

    这篇文章将会介绍Compose的整体结构并一个一个探索这些问题的答案。

    基础概念

    compose编程思想

    Compose是用Kotlin写的,Kotlin版本不低于1.5.10;kotlin支持函数式编程是Compose实现的关键。简介里说了 Compose 是一个声明性框架,也就是声明式或者说函数式编程。他通过函数刷新屏幕上的内容,而不需要拿到组件的具体实例,UI是关于状态的函数,一切都是函数。

    Composable注解

    Compose函数都加了Composable注解修饰。Composable注解的函数只能被另一个Composable注解的函数调用。此注解可告诉编译器,被修饰的函数是Compose函数,用来描述UI界面的。这些函数不返回任何内容,他们旨在描述当前的UI状态。

    微件

    Text()、Image()、Row()、Coulm()等这些描述屏幕元素的函数都被称为微件,类似TextView、ImageView、LinearLayout。从代码上看 view 之间的关系是继承的关系,LinearLayout 继承ViewGroup,ViewGroup继承 View。微件之间没有任何关系,通过组合嵌套自由搭配。在继承关系中有些不必要的属性也会一并继承,这就显的多余。在这一点上,组合组合函数明显更好。

    重组

    在view 体系中如需更改某个组件,可以在该该件上调用 setter 以更改其内部状态。在 Compose 中,则是使用新数据再次调用可组合函数。在Compose中UI刷新的唯一方法就是重组,决定是否重组的条件就是与@Composable元素绑定的数据是否发生了变化。

    简单使用

    实现一个recycleView

    @Composable
    fun showList() {
        //生产 100条数据
        var datalist: MutableList<String> = mutableListOf()
        for (i: Int in 0..100) {
            datalist.add("$i")
        }
        //纵向滑动容器
        LazyColumn {
            //顶部吸顶布局
            stickyHeader {
                Text(text = "顶部吸顶布局",
                     color = Color.Cyan,
                     modifier = Modifier
                         .fillMaxWidth()
                         .background(Color.White),
                     fontSize = 20.sp,
                     textAlign = TextAlign.Center
                )
            }
            //构建列表
            items(datalist) { name ->
                //构建一个item
                BindItemView(name = name)
            }
        }
    }
    
    @Composable
    fun BindItemView(name: String) {
        var imageURL =
            "https://img0.baidu.com/it/u=1766591091,2326601705&fm=253&fmt=auto&app=120&f=JPEG?w=1200&h=674"
        //横向布局 包含文本和图片
        Row(modifier = Modifier
            .padding(10.dp)
            //Item点击事件
            .clickable {
                Toast
                    .makeText(mContext, name, Toast.LENGTH_LONG)
                    .show()
            }) {
            //纵向文本布局
            Column(
                modifier = Modifier
                    .background(Color.White)
                    .weight(1f)
            ) {
                Text(text = "我是个标题$name", fontSize = 16.sp, maxLines = 1)
                Text(
                    color = Purple200,
                    modifier = Modifier.padding(top =6.dp, bottom = 4.dp)
                        .background(Color.White),
                    text = "我是个内容,我可能很长,但是我只能显示最多两行",
                    fontSize = 14.sp,
                    maxLines = 2
                )
                Text(text = "userName", fontSize = 10.sp)
                Spacer(modifier = Modifier.padding(top = 4.dp).fillMaxWidth().height(1.dp).background(Color.Gray))
            }
            //加载一个网络图片
            Image(
                modifier = Modifier
                    .size(80.dp, 60.dp)
                    .align(Alignment.CenterVertically),
                contentScale = ContentScale.Crop,
                painter = rememberImagePainter(imageURL),
                contentDescription = ""
            )
        }
    }
    

    上述60行代码中的Ui,包含了一个纵向滚动的list布局,item有文本有图片,有点击事件,还有顶部吸顶效果。没有xml没有adapter,相比xml-view体系,代码简洁度确实高了不少。


    image.png

    Compose 视图结构

    Comepose抛弃了view体系,那Compose的视图结构是什么样的。

    打开“显示布局边界”可以看到Compose的组件显示了布局边界,我们知道,Flutter与WebView H5内的组件都是不会显示布局边界的,难道Compose最终还是把函数变成了View?

    边框.jpg

    通过android studio 的LayoutInspector看到ComposeActivity的布局结构

    compose_view_layout.png

    最上层还是DecorView、FrameLayout,然后就看到有一个AndroidComposeView,没有TextView或者其他的View了。ComposeActivity 的onCreate方法里setContent替换了原来的setContentView,点击去我们看到

    public fun ComponentActivity.setContent(
        parent: CompositionContext? = null,
        content: @Composable () -> Unit
    ) {
        //decorView 的第一个子View如果是 ComposeView 直接用,如果不是就创建一个ComposeView ,然后添加到跟布局
        val existingComposeView = window.decorView
            .findViewById<ViewGroup>(android.R.id.content)
            .getChildAt(0) as? ComposeView
    
        if (existingComposeView != null) with(existingComposeView) {
           ...
            setContent(content)
        } else ComposeView(this).apply {
           ...
            setContent(content)
           ...
            setOwners()
            setContentView(this, DefaultActivityContentLayoutParams)
        }
    }
    
    

    decorView 的第一个子View如果不是ComposeView就创建一个 ,然后添加到跟布局。而ComposeView 里又通过 Wrapper_android.kt 创建了一个 AndroidComposeView,我们前面见过。ComposeView 和 AndroidComposeView 都是继承ViewGroup,都是在setContent时添加进去的。除此之外我们写的Column,Row,Text并没有出现在布局层级中,也就是说Compose 并没有把函数转成View。AndroidComposeView 可以理解是一个连接view 和 compose 的入口。

    我们知道,View系统通过一个View树的数据结构来存储TextView,ImageView等屏幕元素,渲染的时候去遍历View树。Compose里是怎么管理它的布局元素的呢。

    1. LayoutNote

    我们点开任何一个微件函数,一系列调用最终都会到了Layout.kt的Layout()方法,Layout() 核心是调用ReusableComposeNode ()方法。这里有个参数 factory,factory 是一个构造器函数, factory 被调用就会创建一个LayoutNote,定义的布局属性modifier也在这里设置给了LayoutNode。每个微件最终都是一个LayoutNote。

    @Composable inline fun Layout(
        content: @Composable () -> Unit,
        modifier: Modifier = Modifier,
        measurePolicy: MeasurePolicy
    ) {
        ...
        ReusableComposeNode<ComposeUiNode, Applier<Any>>(
            //1. factory 指向 ComposeUiNode的构造器,创建一个LayoutNode
            factory = ComposeUiNode.Constructor,
            update = {
                set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
                set(density, ComposeUiNode.SetDensity)
                set(layoutDirection, ComposeUiNode.SetLayoutDirection)
                set(viewConfiguration, ComposeUiNode.SetViewConfiguration)
            },
            //2. modifier设置给LayoutNode
            skippableUpdate = materializerOf(modifier),
            content = content
        )
    }
    
    ComposeUiNode #{
         val Constructor: () -> ComposeUiNode = LayoutNode.Constructor
    }
    
    LayoutNode #{
        internal val Constructor: () -> LayoutNode = { LayoutNode() }
    }
    
    1. Composables :ReusableComposeNode方法里又调用Composer 来插入或者更新LayoutNote。
    inline fun <T, reified E : Applier<*>> ReusableComposeNode(
        noinline factory: () -> T,
        update: @DisallowComposableCalls Updater<T>.() -> Unit,
        noinline skippableUpdate: @Composable SkippableUpdater<T>.() -> Unit,
        content: @Composable () -> Unit
    ) {
    
        if (currentComposer.applier !is E) invalidApplier()
        currentComposer.startReusableNode()
    
        //插入一个新节点
        if (currentComposer.inserting) {
            currentComposer.createNode(factory)
        } else {
         //复用节点
            currentComposer.useNode()
        }
        currentComposer.disableReusing()
        //更新节点
        Updater<T>(currentComposer).update()
        currentComposer.enableReusing()
        SkippableUpdater<T>(currentComposer).skippableUpdate()
        currentComposer.startReplaceableGroup(0x7ab4aae9)
        content()
        currentComposer.endReplaceableGroup()
        currentComposer.endNode()
    }
    
    
    1. Composer :这里 factory方法才被执行创建了一个note,获取插入节点的位置,调用nodeApplier插入到root里。
    override fun <T> createNode(factory: () -> T) {
            ...
            //1. 获取插入节点的位置
            val insertIndex = nodeIndexStack.peek()
            val groupAnchor = writer.anchor(writer.parent)
            groupNodeCount++
             recordFixup { applier, slots, _ ->
                @Suppress("UNCHECKED_CAST")
                //2. 在这里才执行了 factory方法, 创建了一个note
                val node = factory()
                slots.updateNode(groupAnchor, node)
                @Suppress("UNCHECKED_CAST") val nodeApplier = applier as Applier<T>
                //3. 调用nodeApplier插入到root里。
                nodeApplier.insertTopDown(insertIndex, node)
                applier.down(node)
            }
           ... 
        }
    
    
    1. Applier
      Composer 里的 nodeApplier 是一个Applier接口,实现类是UiApplier,note 最终是在这里add到 root 节点 (LayoutNote) 的 _foldedChildren 列表里。

    总结一下,compose 构建了一个LayoutNode树,每一个微件函数会生成一个LayoutNode,Composition 作为起点,发起首次的 构图,通过 Composer 的执行填充 NodeTree。渲染引擎基于 LayoutNote 渲染 UI, 每当重构发生时,都会通过 Applier 对 NodeTree 进行更新。简单来说就是 Composer 和 Applier 一起维护了一个 LayoutNote 树。

    Compose在渲染时不会转化成View,而是有一个AndroidComposeView作为入口View,我们声明的Compose布局在渲染时会转化成LayoutNode,所有的屏幕元素都存储在这颗树上。AndroidComposeView 中会触发LayoutNode的布局与绘制。

    compose界面只允许一次测量。

    我们知道View体系有个问题, 如果父布局的布局属性是wrap_content、子布局是match_parent ,父布局先以0为强制宽度测量子View、然后继续测量剩下的其他子View,再用其他子View里最宽的宽度,二次测量这个match_parent的子 View。子View测量了两次,子view也会对下面的子view进行两次测量 。并且随着布局的增长测量次数成指数增长,Compose是如何处理的?

    开头里的例子里我们用了一个线性视图容器Row,我们先试着自定义个AutoRow,实现自动换行的横向布局。以Row为样板,照葫芦画瓢。

    定义一个@Composable 方法:AutoRow
    
    @Composable
    fun AutoRow(modifier: Modifier, content: @Composable () -> Unit) {
        //实例一个 measurePolicy 定义测量和布局策略
        var measurePolicy = object : MeasurePolicy{
            //实现 measure 方法,
            // Measurable : 是一个接口,通过断点可以看到,它的实例就是一个 LayoutNote,childs 就是子节点列表。
            // constraints : 是父布局的约束条件,有点像View 的 MeasureSpec
            override fun MeasureScope.measure(
                childs: List<Measurable>,
                constraints: Constraints
            ): MeasureResult {
                var placeables = mutableListOf<Placeable>()
                childs.forEach {
                    //1.遍历测量子项,得到一个可放置项 Placeable
                    // measure()方法传入一个Constraints:约束。 测量完成后返回 一个 Placeable
                    // 一次测量过程中,多次调用一个子项的measure()方法会抛异常
                   var constraints = Constraints(0, constraints.maxWidth, 0, constraints.maxHeight)
                    val placeable = it.measure(constraints)
                    placeables.add(placeable)
                }
                var layoutX = 0
                var layoutY = 0
                var padding = 20
                //2.layout 方法确定布局的大小,把测量得到的Placeable摆放到指定位置
                val measureResult = layout(width = constraints.minWidth, height = constraints.minHeight,placementBlock={
                    placeables.forEach { placeable ->
                        if (layoutX + placeable.width > constraints.maxWidth) {
                            layoutX = 0
                            layoutY += placeable.height
                        }
                        // 3.定位子项在父布局位置的位置
                        placeable.placeRelative(layoutX, layoutY)
                        layoutX += placeable.width + padding
                    }
                })
                return measureResult
            }
    
        }
        //4.布局
        //上面这些事定义了measure()、layout() 的策略,Layout这里才触发执行的。
        Layout(content = content, modifier = modifier, measurePolicy = measurePolicy)
    }
    
    自动换行.png

    测量的 measure () 方法最终调用了 outerMeasurablePlaceable.measure(),其中check()方法里判断了当前节点是否正在被父布局测量,如果正在测量就会抛出异常。

        override fun measure(constraints: Constraints) =
            outerMeasurablePlaceable.measure(constraints)
    
        //  OuterMeasurablePlaceable ##
        override fun measure(constraints: Constraints): Placeable {
            // when we measure the root it is like the virtual parent is currently laying out
            val parent = layoutNode.parent
            if (parent != null) {
                check(
                    layoutNode.measuredByParent == LayoutNode.UsageByParent.NotUsed ||
                        @Suppress("DEPRECATION") layoutNode.canMultiMeasure
                ) {
                    "measure() may not be called multiple times on the same Measurable. Current " +
                        "state ${layoutNode.measuredByParent}. Parent state ${parent.layoutState}."
                }
                ...
        }
    
    

    其实Measurable的注释讲的很清楚

    Measures the layout with constraints, returning a Placeable layout that has its new size. A Measurable can only be measured once inside a layout pass.

    使用约束测量布局,返回具有新大小的可放置布局。一个Measurable 只能测量一次在一次layout 过程里。
    这是Compose的特性,就只允许一次测量,从根本上解决了布局嵌套多次测量的问题。

    测量过程分析

    Modifier 链

    我们构造一个compose 微件时,有一个参数很重要,他描述了这个微件的一些特性。比如:Text()通过 Modifier设置了size,padding,background等等。

       Text( modifier = Modifier
               .size(80.dp, 20.dp)
               .padding(10.dp)
               .background(Color.White),
             text = "我是个内容,我可能很长,但是我只能显示最多两行",
       )
    
        fun Modifier.size(width: Dp, height: Dp) = this.then(
            SizeModifier(
                minWidth = width,
                maxWidth = width,
                minHeight = height,
                maxHeight = height,
                enforceIncoming = true,
                inspectorInfo = debugInspectorInfo {
                    name = "size"
                    properties["width"] = width
                    properties["height"] = height
                }
            )
        )
        
        infix fun then(other: Modifier): Modifier =
            if (other === Modifier) this else CombinedModifier(this, other)
    
    class CombinedModifier(
        private val outer: Modifier,
        private val inner: Modifier
    ) : Modifier {...}
    

    size(),padding()可以理解成操作符,类似rxjava 里的那样。点进去会看到在这些方法里创建了对应的 SizeModifier,PaddingModifier,他们都继承自LayoutModifier(表示Layout相关的属性),background 里创建了Background 继承DrawModifier(表示绘制相关的属性),LayoutModifier、DrawModifier 继承Modifier.Element。SizeModifier,PaddingModifier、Background被包进CombinedModifier,形成了一条Modifier链。

    Modifier链.png

    LayoutNodeWrapper链

    我们在前面Layout 里看到,modifier(链)被传入materializerOf()方法,最终被赋值给LayoutNote.modifier这个成员变量。
    当被set()方法设置给LayoutNote时 ,

    1. 调用foldOut()方法遍历modifier链,把不同的Modifier类型包装成Node,LayoutModifier包装成ModifiedLayoutNode,
    2. ModifiedLayoutNode 挂在初始节点 innerLayoutNodeWrapper:LayoutNodeWrapper 上,形成一条Wrapper链,innerLayoutNodeWrapper在链尾。
    3. 遍历完成后把链头设置给outerMeasurablePlaceable.outerWrapper。
    override var modifier: Modifier = Modifier
            set(value) {// 成员变量modifier 的set()方法
               ...
                //1. foldOut()遍历 modifier 每个节点
                val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod, toWrap ->
                  
                   // 2.  类型判断,构建Node节点,挂在初始节innerLayoutNodeWrapper上
                    if (mod is DrawModifier) {
                        val drawEntity = DrawEntity(toWrap, mod)
                        drawEntity.next = toWrap.drawEntityHead
                        toWrap.drawEntityHead = drawEntity
                        drawEntity.onInitialize()
                    }
                    //toWrap = innerLayoutNodeWrapper
                    var wrapper = toWrap
                   ... 
                  //省略了一些Modifier类型
                    
                   // 不同的Modifier类型被包装成不同类型的Node,
                   //LayoutModifier被包装成了ModifiedLayoutNode,
                  if (mod is FocusEventModifier) {
                        wrapper = ModifiedFocusEventNode(wrapper, mod)
                            .initialize()
                            .assignChained(toWrap)
                    }
                    if (mod is FocusRequesterModifier) {
                        wrapper = ModifiedFocusRequesterNode(wrapper, mod)
                            .initialize()
                            .assignChained(toWrap)
                    }
                    if (mod is FocusOrderModifier) {
                        wrapper = ModifiedFocusOrderNode(wrapper, mod)
                            .initialize()
                            .assignChained(toWrap)
                    }
                    if (mod is LayoutModifier) {
                        wrapper = ModifiedLayoutNode(wrapper, mod)
                            .initialize()
                            .assignChained(toWrap)
                    }
                   ...
                   //3. 返回链头
                    wrapper
                }
    
                outerWrapper.wrappedBy = parent?.innerLayoutNodeWrapper
               //outerWrapper是链头,innerLayoutNodeWrapper在链尾
                outerMeasurablePlaceable.outerWrapper = outerWrapper
                 ...
            }
    

    画成图如下:


    measure.png

    测量入口

    Layout()里把modifier设置给了LayoutNode,还把modifier转成了LayoutNodeWrapper链,那什么时候使用这些Modifier的呢。我们开始的地方知道了ComposeActivity布局的顶层还是View,那就是说视图的最顶层的渲染还是依赖view的。在 AndroidComposeView 看到了 dispatchDraw(),这就是Compose 渲染的入口。这里会触发布局测量。

        override fun dispatchDraw(canvas: android.graphics.Canvas) {
            //Compose 测量与布局入口
            measureAndLayout()
            
            //Compose 绘制入口
            canvasHolder.drawInto(canvas) { root.draw(this) }
            //...
        }
    
    

    测量布局过程

    measureAndLayout()遍历所有需要测量的 LayoutNote,最终到MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded()方法,测量并布局每一个LayoutNote。

    private fun remeasureAndRelayoutIfNeeded(layoutNode: LayoutNode): Boolean {
            var sizeChanged = false
            if (layoutNode.isPlaced ||
                layoutNode.canAffectParent ||
                layoutNode.alignmentLines.required
            ) {
                if (layoutNode.layoutState == NeedsRemeasure) {
                    //1. 测量 layoutNode
                    sizeChanged = doRemeasure(layoutNode)
                }
                if (layoutNode.layoutState == NeedsRelayout && layoutNode.isPlaced) {
                     //2. 设置 layoutNode 位置
                    if (layoutNode === root) {
                        layoutNode.place(0, 0)
                    } else {
                        layoutNode.replace()
                    }
                    onPositionedDispatcher.onNodePositioned(layoutNode)
                    consistencyChecker?.assertConsistent()
                }
                。。。
            }
            return sizeChanged
        }
        
    
    1. 测量
      doRemeasure() 调用了 outerMeasurablePlaceabl.remeasure(),
      outerMeasurablePlaceabl 是 LayoutNote 的一个成员,outerMeasurablePlaceabl 又调用outerWrapper.measure()。上面说到过,outerWrapper 是头节点 LayoutModifierNode 。
    internal class ModifiedLayoutNode(
        wrapped: LayoutNodeWrapper,
        modifier: LayoutModifier
    ) : DelegatingLayoutNodeWrapper<LayoutModifier>(wrapped, modifier) {
    
        override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
            with(modifier) { 
                measureResult = measureScope.measure(wrapped, constraints)
                this@ModifiedLayoutNode
            }
        }
    
    

    根据我们写的代码,此时modifier 为 SizeModifier, 类型 LayoutModifier.kt

    SizeModifier.kt

      private class SizeModifier(
        private val minWidth: Dp = Dp.Unspecified,
        private val minHeight: Dp = Dp.Unspecified,
        private val maxWidth: Dp = Dp.Unspecified,
        private val maxHeight: Dp = Dp.Unspecified,
        private val enforceIncoming: Boolean,
        inspectorInfo: InspectorInfo.() -> Unit
    ) : LayoutModifier, InspectorValueInfo(inspectorInfo) {
        override fun MeasureScope.measure(
            measurable: Measurable,/*下一个LayoutNodeWrapper*/
            constraints: Constraints/* 父容器或者上一个节点的约束 */
        ): MeasureResult {
            val wrappedConstraints = targetConstraints.let { targetConstraints ->
                if (enforceIncoming) {//当我们给控件指定大小时,这个值就为true
                    //结合父容器或者上一个节点的约束 和我们指定约束进行结合生成一个新的约束
                    constraints.constrain(targetConstraints)
                } else {
                    ……
                }
            }
            // 进行下一个 LayoutNodeWrapper 节点测量
            val placeable = measurable.measure(wrappedConstraints)
    
            //测量完,开始摆放节点的位置
            return layout(placeable.width, placeable.height) {
                placeable.placeRelative(0, 0)
            }
        }
    

    SizeModifier.measure自己测量完,进行下一个 LayoutNodeWrapper 节点的测量,一直到最后 InnerPlaceable 节点。InnerPlaceable触发LayoutNode子项的测量,这里就是Layout 的MeasurePolicy 的measure 执行的地方了

    class InnerPlaceable{   
        override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
            val measureResult = with(layoutNode.measurePolicy) {
                // 触发LayoutNode子项的测量,这里就是Layout 的MeasurePolicy 的measure 执行的地方了
                layoutNode.measureScope.measure(layoutNode.children, constraints)
            }
            layoutNode.handleMeasureResult(measureResult)
            return this
        }
    }
    
    
    1. 布局
    internal fun replace() {
            try {
                relayoutWithoutParentInProgress = true
                outerMeasurablePlaceable.replace()
            } finally {
                relayoutWithoutParentInProgress = false
            }
        }
    

    Layout. replace()方法,看到布局过程也是由outerMeasurablePlaceable完成的。和测量过程一样,过程就不一步步的分析了,经过一系列的链式调用,最终到 InnerPlaaceable.placeAt()。

    1. InnerPlaaceable.placeAt(),设置 当前节点的位置。

    2. onNodePlaced()最终会调用 MeasureResult.placeChildren(),触发子项位置的摆放。

        override fun placeAt(
            position: IntOffset,
            zIndex: Float,
            layerBlock: (GraphicsLayerScope.() -> Unit)?
        ) { 
            //1.往下掉用LayoutNoteWrapper.placeAt(),设置当前节点的 的位置
            super.placeAt(position, zIndex, layerBlock)
    
           //2. 最终会调用 MeasureResult.placeChildren(),设置子项的位置。
            layoutNode.onNodePlaced()
        }
    
    1. 绘制
      这些布局是怎么绘制到屏幕上的呢?在AndroidComposeView里看到绘制的入口是
     canvasHolder.drawInto(canvas) { root.draw(this) }
    

    root是LayoutNode的根节点。调用了LayoutNode.draw()方法,还传了个参数:canvas。LayoutNode继续调用outerLayoutNodeWrapper.draw(canvas),看到这应该能猜到了,绘制应该也是LayoutNodeWrapper完成的。还记得backgroung方法创建了BackGround继承DrawModifier,在LayoutNode里被包装成了DrawEntity

    internal class DrawEntity(
        val layoutNodeWrapper: LayoutNodeWrapper,//下一个节点
        val modifier: DrawModifier
    ) : OwnerScope {
    

    最终还是调用到了LayoutNodeWrapper.draw()

        fun draw(canvas: Canvas) {
            val layer = layer
            if (layer != null) {
                layer.drawLayer(canvas)
            } else {
                val x = position.x.toFloat()
                val y = position.y.toFloat()
                canvas.translate(x, y)
                drawContainedDrawModifiers(canvas)
                canvas.translate(-x, -y)
            }
        }
    

    我们看到绘制还是基于canvas的。

    总结:

    1. 在setContent的过程中,会创建ComposeView与AndroidComposeView,其中AndroidComposeView是Compose的入口,连接View体系和新的Compose体系。
    2. Compose虽然没有了view 树,但也需要一个数据结构来存储Text、Image等屏幕元素,那就是LayoutNote树。
    3. 微件里设置的modifier会构建一个LayoutModifier链,在设置进LayoutNode时转换成了LayoutNodeWrapper链,测量和布局是由一个个LayoutNodeWrapper节点按顺序完成的,最后一个节点InnerPlaaceable触发子项的测量和布局。
    4. Compose 特性禁止在一次Layout 过程中多次测量一个子项,这从根本上解决了布局嵌套多次测量的问题。
    5. Compose绘制虽然脱离了View,但还是依赖canvas。

    文章到这里结束了,开头的那些问题也都有了答案。

    相关文章

      网友评论

        本文标题:一文了解Compose

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