• Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

1. Introduction

In Android, you can use view #bringToFront() to display a view at the top of the parent control.

Compose is not called: BringToFront, instead of using Modifier to control the drawing order of subitems. While implementing bringToFront in Compose is simple, simplicity doesn’t mean that we don’t have to think about the implementation, we know what it does. When you encounter problems, you can solve them better.

For example, Jetpack Compose UI is composed for composing a new layout. For example, Jetpack Compose UI is composed for composing a new layout.

To implement bringToFront, we need to use the Modifier. ZIndex Modifier to briefly look at the official explanation:

Control the drawing order of subitems. Those with a larger zIndex will be drawn on top of those with the same zIndex in the order in which they are placed. ZIndex defaults to 0

2. Examples of simple demonstrations

One, simply write a StickerNode method

@Composable
fun StickerNode(modifier: Modifier, content: String, index: Int, onClick: () -> Unit) {
    Box(
        // Use the Modifier must pay attention to the order oh
        modifier.offset(if(index == 0) 30.dp else 100.dp,if(index == 0) 100.dp else 40.dp)
            .clickable {
                onClick()
            }
            .border(width = 1.dp, color = Color.Black)
            .background(
                if (index == 0) {
                    Color(android.graphics.Color.parseColor("#2196f4"))}else {
                    Color(android.graphics.Color.parseColor("#fd9801"))
                }
            )
    ) {
        Text(
            text = content,
            modifier = Modifier.padding(40.dp),
            color = Color.White,
            style = MaterialTheme.typography.subtitle2
        )
    }
}
Copy the code

A simple example call is as follows:

    val list = mutableListOf("X"."Bai Suzhen")
    var focusIndex by remember { mutableStateOf(0) }
    Box(modifier = Modifier.fillMaxSize()) {
        list.forEachIndexed {index,value->
            StickerNode(modifier = Modifier.zIndex(if(focusIndex == index) 1F else 0F),content = value,index){
                // Update the index displayed in the upper layer
                focusedIndex = index
            }
        }
    }
Copy the code

The demo effect is as follows:





bringToFront

2. Update the zIndex value

When the Modifier. ZIndex value is updated, it triggers the change, which is suggested that you can go to see Jetpack Compose UI creation layout process + principle. Let’s take a quick look at some of the executed logic

//androidx.compose.runtime.Recomposer

suspend fun runRecomposeAndApplyChanges(a) = recompositionRunner { parentFrameClock ->
    while (shouldKeepRecomposing) {
        ......
        try {
                 // Perform the changetoApply.fastForEach { composition -> composition.applyChanges() } } ...... }}Copy the code

Trigger an applyChanges

//androidx.compose.runtime.CompositionImpl
fun applyChanges(a){... slotTable.write { slots ->val applier = applier
        changes.fastForEach { change ->
            // Invoke is triggered and the modifier is changed
            // The setModifier method in LayoutNode is triggered
            change(applier, slots, manager)
        }
        changes.clear()
    }
    ......
}
Copy the code

Perform LayoutNode#setModifier, then perform requestRemeasure() and parent? RequestRelayout (requestRelayout, requestRelayout, requestRelayout

//androidx.compose.ui.node.LayoutNode#requestRemeasure

internal fun requestRemeasure(a) {
    valowner = owner ? :return
    if(! ignoreRemeasureRequests && ! isVirtual) {// Owner here => AndroidComposeView
       owner.onRequestMeasure(this)}}Copy the code

RequestRemeasure is executed internally

//androidx.compose.ui.node.MeasureAndLayoutDelegate

fun requestRemeasure(layoutNode: LayoutNode): Boolean = when (layoutNode.layoutState) {
    ......
    NeedsRelayout, Ready -> {
            .......
            layoutNode.layoutState = NeedsRemeasure
            if (layoutNode.isPlaced || layoutNode.canAffectParent) {
                valparentLayoutState = layoutNode.parent? .layoutStateif(parentLayoutState ! = NeedsRemeasure) {// The parent node's layoutState does not call remeasurement and needs to be added to the relayoutNodes list
                    relayoutNodes.add(layoutNode)
                }
            }
    }
    .......
}
Copy the code

Then execute parent? .requestrelayout, which will eventually execute the following code internally

//androidx.compose.ui.node.MeasureAndLayoutDelegate#requestRelayout
fun requestRelayout(layoutNode: LayoutNode): Boolean = when (layoutNode.layoutState) {
    ......
    Ready -> {
            ......
            layoutNode.layoutState = NeedsRelayout
            if (layoutNode.isPlaced) {
                valparentLayoutState = layoutNode.parent? .layoutStateif(parentLayoutState ! = NeedsRemeasure && parentLayoutState ! = NeedsRelayout) {// If the above condition is met, add it to the relayoutNodes listrelayoutNodes.add(layoutNode) } } ...... }... }Copy the code

Both onRequestMeasure(layoutNode) and onRequestLayout(layoutNode) call AndroidComposeView#scheduleMeasureAndLayout() internally. Trigger invalidate ()

This will eventually trigger the AndroidComposeView#dispatchDraw call

//androidx.compose.ui.platform.AndroidComposeView
override fun dispatchDraw(canvas: android.graphics.Canvas){...// Internally execute relayoutNodes to traverse node nodes by tree depth priority
    measureAndLayout()
    // Execute the LayoutNode drawing, which is sorted by zIndex in LayoutNode
    canvasHolder.drawInto(canvas) { root.draw(this)}... }Copy the code

We look at the following flow chart, dispatchDraw inside to execute things, you can according to the following flow chart, go into the code inside the details:





Draws the order of method calls

Conclusion 3.

(1). After updating the Modifier. ZIndex value, the Modifier will trigger the reorganizing application change. (2). The Modifier value is changed, so LayoutNode will update the Modifier value; (3) If the conditions are met, the requestRemeasure and LayoutNode requestRelayout will be triggered, and LayoutNode will be added to the List of relayoutNodes. AndroidComposeView#scheduleMeasureAndLayout() calls invalidate() and triggers dispatchDraw(canvas) (5). Within the dispatchDraw method, the relayoutNodes list is first traversed to perform measurement and layout, followed by the layoutNode list, and the LayoutNode.draw(Canvas) node is drawn according to the zIndex in the layoutNode


Previous articles recommended: 2.Jetpack Compose UI creation layout drawing process + principle – containing concept details (full of dry stuff) 3.Jetpack App Startup how to use and principle analysis 4 Comy-strings IST Kit library 5. Source code analysis | ThreadedRenderer null pointer, the Choreographer to meet 6, by the way. Source code analysis | events is how to the Activity? 7. Do not fall into the loop of the need to keep alive