Does Compose ConstraintLayout provide same handling for INVISIBLE and GONE as view based ConstraintLayout?

Viewed 1916

Disclaimer: I know that Compose just entered alpha01 and thus I do not expect that every functionality is available. However, layout and handling of specific layout cases is IMHO an important topic that should be addressed early .

The current view based ConstraintLayout provides some specific handling in case a child view is marked as GONE, refer to the ConstrainLayout documentation.

I checked the Compose ConstraintLayout documentation, the available modifiers and so on, however did not find anything that points in this direction. I also could not find any hint regarding INVISIBLE and how/if Compose ConstraintLayout handles it like the view based ConstraintLayout.

In general, the current view based layouts (LinearLayout for example) handle INVISIBLE and GONE in a similar fashion:

  • if a view is in state INVISIBLE then the view is part of the layout with its sizes, just not shown. The overall layout of other views does not change and they stay in their positions.

  • if a view is in state GONE its sizes a usually treated as 0 and the layout is recomputed and changed, other views usually change their positions.

Here a simple Compose ConstraintLayout UI, just 4 buttons in a row, chained to have them nicely spread.

// if dontShow is 0 then show all buttons, otherwise make the button with this number
// somehow INVISIBLE. This feature is not yet implemented.
@Composable
fun fourButtonsCL(dontShow: Int) {
    ConstraintLayout(Modifier.fillMaxSize()) {
        val (btn1, btn2, btn3, btn4) = createRefs()
        TextButton(onClick = {}, Modifier.constrainAs(btn1) {}.background(teal200)) { Text("Button1") }
        TextButton(onClick = {}, Modifier.constrainAs(btn2) {}.background(teal200)) { Text("Button2") }
        TextButton(onClick = {}, Modifier.constrainAs(btn3) {}.background(teal200)) { Text("Button3") }
        TextButton(onClick = {}, Modifier.constrainAs(btn4) {}.background(teal200)) { Text("Button4") }

        createHorizontalChain(btn1, btn2, btn3, btn4)
    }
}

@Preview(showBackground = true)
@Composable
fun previewThreeButtons() {
    ComposeAppTheme {
        fourButtonsCL()
    }
}

Assume I would like to make Button3 invisible, but keep the other 3 buttons positioned where they are. Thus just a hole between Button2 and Button4. How to achieve this without creating yet another Composable or adding additional logic. While the logic in this simple case may be just a view lines of code, more complex layouts would the need some more complex logic. In veiw based ConstraintLayout wwe just need to modify the child view.

The other assumption: make Button3 disappear completely from the layout (GONE) and re-compute the layout, remaining buttons become wider and evenly spread out. At a first glance this looks simple, and in this very simple example it maybe easy. However in more complex layouts this could require some or even a lot of re-wiring of constraints of the embedded Composables.

Thus the question is: how does compose handles these cases for Column and Row layouts (like in view based LinearLayout) and for ConstraintLayout in particular? However with the following restriction : without defining many new Composables and/or without adding complex layout logic inside the Composables (re-wiring constraints for example).

Did I miss some modifier? Is this even planned or possible in the Composable layouts? What would be the preferred way to solve such layout cases in Compose?

1 Answers

Based on @CommonsWare comment to the question I could solve the INVISIBLE option, see code below.

Currently (in alpha-01) the implementation of ConstraintLayout seems to be incomplete, at least a few TODO comments in the code indicate this. This seems to include the yet missing support of the GONE feature. I saw some of these:

// TODO(popam, b/158069248): add parameter for gone margin

Also the chain feature does not yet perform layout rendering in the same way as in the view based ConstraintLayout.

object FourElementsNoDSL {
    const val elementA = "ElementA"
    const val elementB = "ElementB"
    const val elementC = "ElementC"
    const val elementD = "ElementD"

    private val noDSLConstraintSet = ConstraintSet {

        // Create references with defines ids, here using a string as id. Could be an Int as well,
        // actually it's defined as 'Any'
        val elemA = createRefFor(elementA)
        val elemB = createRefFor(elementB)
        val elemC = createRefFor(elementC)
        val elemD = createRefFor(elementD)

        // Simple chain only. Instead of this simple chain we can use (for example):
        //     constrain(elemA) {start.linkTo(parent.start) }
        // to set a constraint as known in XML

//        constrain(elemA) {start.linkTo(parent.start, 16.dp) }
//        constrain(elemB) {start.linkTo(elemA.end) }
//        constrain(elemC) {start.linkTo(elemB.end) }
//        constrain(elemD) {end.linkTo(parent.end) }
        createHorizontalChain(elemA, elemB, elemC, elemD)
    }

    @Composable
    fun fourButtonsCLNoDSL(doNotShow: List<String>) {
        ConstraintLayout(constraintSet = noDSLConstraintSet, modifier = Modifier.fillMaxSize()) {

            // This block contains the children
            Text(text = "A",
                    modifier = Modifier.layoutId(elementA)
                            .drawOpacity(if (doNotShow.contains(elementA)) 0f else 1f)
                            .padding(0.dp),
                    style = TextStyle(fontSize = 20.sp)
            )
            Text(text = "B",
                    modifier = Modifier.layoutId(elementB)
                            .drawOpacity(if (doNotShow.contains(elementB)) 0f else 1f)
                            .padding(0.dp),
                    style = TextStyle(fontSize = 20.sp)
            )
            Text(text = "C",
                    modifier = Modifier.layoutId(elementC)
                            .drawOpacity(if (doNotShow.contains(elementC)) 0f else 1f)
                            .padding(0.dp),
                    style = TextStyle(fontSize = 20.sp)
            )
            Text(text = "D",
                    modifier = Modifier.layoutId(elementD)
                            .drawOpacity(if (doNotShow.contains(elementD)) 0f else 1f)
                            .padding(0.dp),
                    style = TextStyle(fontSize = 20.sp))

        }
    }
}

@Preview(showBackground = true)
@Composable
fun previewFourFieldsNoDSL() {
    val noShow = listOf(FourElementsNoDSL.elementC)
    PlaygroundTheme {
        FourElementsNoDSL.fourButtonsCLNoDSL(noShow)
    }
}

The object FourElementsNoDSL defines the layout, provides element ids and so on. This is roughly comparable to an XML file that contains such layout. noDSL means, that this layout does not use the Compose ConstraintLayout's Kotlin DSL. Currently the DSL does not provide a mechanism to setup element references (used in layoutId) with defined ids as its done in this example.

Related