Does Modifier.zIndex() has a lambda version? I wan...
# compose
j
Does Modifier.zIndex() has a lambda version? I want to make a carousel like this, but when i scroll, the card always get recomposed.
s
Looking at the impl, all there is to
zIndex
modifier is this:
Copy code
@Stable
fun Modifier.zIndex(zIndex: Float): Modifier = this then ZIndexElement(zIndex = zIndex)

internal data class ZIndexElement(val zIndex: Float) : ModifierNodeElement<ZIndexNode>() {
    override fun create() = ZIndexNode(zIndex)
    override fun update(node: ZIndexNode) {
        node.zIndex = zIndex
    }
    override fun InspectorInfo.inspectableProperties() {
        name = "zIndex"
        properties["zIndex"] = zIndex
    }
}

internal class ZIndexNode(var zIndex: Float) : LayoutModifierNode, Modifier.Node() {
    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints
    ): MeasureResult {
        val placeable = measurable.measure(constraints)
        return layout(placeable.width, placeable.height) {
            placeable.place(0, 0, zIndex = zIndex)
        }
    }

    override fun toString(): String = "ZIndexModifier(zIndex=$zIndex)"
}
You can probably make your own modifier which takes in a lambda instead.
If you literally just swap the
Float
instances with
() -> Float
, does it work? Give it a shot, I am also curious.
Copy code
@Stable
private fun Modifier.zIndex(zIndex: () -> Float): Modifier = this then ZIndexElement(zIndex = zIndex)

private data class ZIndexElement(val zIndex: () -> Float) : ModifierNodeElement<ZIndexNode>() {
  override fun create() = ZIndexNode(zIndex)

  override fun update(node: ZIndexNode) {
    node.zIndex = zIndex
  }

  override fun InspectorInfo.inspectableProperties() {
    name = "zIndex"
    properties["zIndex"] = zIndex
  }
}

private class ZIndexNode(var zIndex: () -> Float) : LayoutModifierNode, Modifier.Node() {
  override fun MeasureScope.measure(measurable: Measurable, constraints: Constraints): MeasureResult {
    val placeable = measurable.measure(constraints)
    return layout(placeable.width, placeable.height) {
      placeable.place(0, 0, zIndex = zIndex())
    }
  }

  override fun toString(): String = "ZIndexModifier(zIndex=$zIndex)"
}
I don't know if you'd need to invalidate something yourself. Or if you need to change anything inside inspectableProperties for example, from
properties["zIndex"] = zIndex
to
properties["zIndex"] = zIndex()
I haven't played a lot around using the new modifier APIs myself
j
No, it does not work, but thank you. It seems like it always need to measure before placing, so it cannot be skipped
s
> No, it does not work I just tested it, and it does work actually 🤷 How are you providing the zIndex to the lambda? If you are already reading it somewhere else this won't help. Here's a composable which logs on each recomposition
Copy code
class Ref(var value: Int)

@Suppress("NOTHING_TO_INLINE")
@Composable
inline fun LogCompositions(message: String) {
  val ref = remember { Ref(0) }
  SideEffect { ref.value++ }
  Log.d("", "Debug Log Compositions: $message ${ref.value}")
}
Then the test code
Copy code
@Composable
fun Test() {
  val transition = rememberInfiniteTransition()
  val redIndex by transition.animateFloat(
    0f,
    2f,
    infiniteRepeatable(tween(1000), Reverse),
  )
  var blueIndex by remember { mutableFloatStateOf(1f) }

  LogCompositions("recomp")
  Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
    Box(
      Modifier
        .size(50.dp)
        .background(androidx.compose.ui.graphics.Color.Red)
        .zIndex(redIndex),
    )
    Box(
      Modifier
        .offset { IntOffset(16.dp.roundToPx(), 16.dp.roundToPx()) }
        .size(50.dp)
        .background(androidx.compose.ui.graphics.Color.Blue)
        .zIndex(blueIndex),
    )
  }
}
This uses the normal zIndex, and logs hundreds of recompositions within just seconds.
Then if you replace the test code with this instead
Copy code
@Composable
fun Test() {
  val transition = rememberInfiniteTransition()
  val redIndex by transition.animateFloat(
    0f,
    2f,
    infiniteRepeatable(tween(1000), Reverse),
  )
  var blueIndex by remember { mutableFloatStateOf(1f) }

  LogCompositions("recomp")
  Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
    Box(
      Modifier
        .size(50.dp)
        .background(androidx.compose.ui.graphics.Color.Red)
        .zIndex { redIndex },
    )
    Box(
      Modifier
        .offset { IntOffset(16.dp.roundToPx(), 16.dp.roundToPx()) }
        .size(50.dp)
        .background(androidx.compose.ui.graphics.Color.Blue)
        .zIndex { blueIndex },
    )
  }
}
It will just do 1 recomposition, and the UI does change where the blue and the red box switch places as the Z index goes over or under 1f. Run it yourself to check it out
j
Oh, thank you it is working now, i misunderstood that you just provide the float values in the lambda like this
Copy code
fun comp(idx: () -> Float) {
    Box(Modifier.zIndex(idx()))
}
I didnt realize i need to add the Modifier zIndex lambda implementation myself, 😅 Thank you anyways!
s
() -> T
is a key to deferring reads indeed, some more context in this old discussion https://kotlinlang.slack.com/archives/CJLTWPH7S/p1637855772048800?thread_ts=1637854750.046000&amp;cid=CJLTWPH7S