0

In the shopping list item composable, I am reducing the alpha value on the whole item by changing the alpha value on it's parent Row composable, but I want to exclude a child Icon composable from receiving the parent Row's alpha change and retain a 100% alpha value. I set the modifier on the child Icon to alpha(1f), but it is not working. The alpha change on the parent is also propagating to the child despite this. Is it possible exclude the child from the parent's alpha change?

Composable

@Composable
fun ShoppingListScreenItem(
    rowModifier: Modifier = Modifier,
    item: ShoppingListItem,
    mainViewModel: ShoppingListScreenViewModel,
    onNavigateToAddEditItemScreenFromItemStrip: (ShoppingListItem) -> Unit,
) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(start = 3.dp, bottom = 5.dp)
            .then(rowModifier)
            .alpha(if (item.isItemDisabled) 0.35f else 1f)
            .clickable {
                onNavigateToAddEditItemScreenFromItemStrip(item)
            },
        verticalAlignment = Alignment.CenterVertically,
    ) {
        ...
     
        if(mainViewModel.shoppingListState.value!!.sortOrder != "Custom" && !item.isInCart) {

            //I want to exclude this child composable
            IconToggleButton(
                checked = item.isItemDisabled,
                onCheckedChange = {
                    scope.launch {
                        mainViewModel.updateShoppingListItemDisabledInDb(item, it)
                    }
                },
            ) {
                Icon(
                    modifier = Modifier.alpha(1f).size(26.dp),
                    painter = painterResource(id = R.drawable.baseline_disabled_visible_24),
                    contentDescription = "Toggle disable the item strip",
                )
            }
        }
    }
}
Raj Narayanan
  • 2,443
  • 4
  • 24
  • 43
  • I really can't understand why you should write a not reproducible example. – Gabriele Mariotti Jan 13 '23 at 18:48
  • The entire composable is too big. – Raj Narayanan Jan 13 '23 at 18:49
  • I don't think the problem is the composable too big. Just clean your code removing the viewmodel and local references. A user can't help you without reworking your code. – Gabriele Mariotti Jan 13 '23 at 18:52
  • Remove the stuff that's irrelevant to the issue - padding modifiers, alignment parameters, clickables, anything that is decorative or pertains to the business logic, since that's not what is connected to the issue. – Richard Onslow Roper Jan 13 '23 at 19:39
  • You probably want to use `CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) { .. }` - https://developer.android.com/jetpack/compose/compositionlocal – Mark Jan 13 '23 at 19:57
  • @Mark Thanks for pointing this out, but it doesn't work if the parent composable set's its own alpha value as is my case. – Raj Narayanan Jan 13 '23 at 21:02
  • @Mark To be more precise, this approach works only if setting the child alpha value to anything below that of the parent. If it's above the parent's alpha value, it just displays at the same alpha as the parent. – Raj Narayanan Jan 13 '23 at 21:15
  • @GabrieleMariotti Thanks for bringing that to my attention! – Raj Narayanan Jan 15 '23 at 12:11
  • 1
    You can apply the alpha to all children except one, instead of applying it to the parent container – Gabriele Mariotti Jan 15 '23 at 13:32

1 Answers1

1

This can be achieved using Layout and placeable.placeWithLayer and Modifier.layoutId to select which Composable is to be used with default alpha as.

This is a custom Column, you can customize Layout as required, purpose is to show Modifier.layoutId usage and Placeable.placeRelativeWithLayer to apply any desired graphic layer property to specific Composable in layout phase.

Result

enter image description here

Usage

MyLayout(
    alpha = .5f
) {
    Text("Default Alpha", fontSize = 20.sp)
    Text("Default Alpha", fontSize = 20.sp)
    Text("Custom Alpha", fontSize = 20.sp), modifier = Modifier.layoutId("full_alpha"))
    Text("Default Alpha", fontSize = 20.sp)

    Image(painter = painterResource(id = R.drawable.landscape5), contentDescription = "")
}

Implementation

@Composable
private fun MyLayout(
    modifier: Modifier = Modifier,
    alpha: Float = 1f,
    content: @Composable () -> Unit
) {

    val measurePolicy = MeasurePolicy { measurables, constraints ->

        val fullAlphaIndex = measurables.indexOfFirst {
            it.layoutId == "full_alpha"
        }
        val placeablesWidth = measurables.map { measurable ->
            measurable.measure(constraints)
        }

        val hasBoundedWidth = constraints.hasBoundedWidth
        val hasFixedWidth = constraints.hasFixedWidth


        val width =
            if (hasBoundedWidth && hasFixedWidth) constraints.maxWidth
            else placeablesWidth.maxOf { it.width }

        val height = placeablesWidth.sumOf {
            it.height
        }

        var posY = 0

        layout(width, height) {
            placeablesWidth.forEachIndexed { index, placeable ->
                placeable.placeRelativeWithLayer(0, posY) {
                    if (index == fullAlphaIndex) {
                        this.alpha = 1f
                    } else {
                        this.alpha = alpha
                    }
                }

                posY += placeable.height
            }
        }

    }

    Layout(
        modifier = modifier,
        content = content,
        measurePolicy = measurePolicy
    )
}

If you wish to create a Row you need to place items one after other horizontally instead of increasing y position

Result

enter image description here

@Composable
fun MyLayout(
    modifier: Modifier = Modifier,
    alpha: Float = 1f,
    content: @Composable () -> Unit
) {
    val measurePolicy = MeasurePolicy { measurables, constraints ->

        val fullAlphaIndex = measurables.indexOfFirst {
            it.layoutId == "full_alpha"
        }
        val placeablesWidth = measurables.map { measurable ->
            measurable.measure(
                constraints.copy(
                    minWidth = 0,
                    maxWidth = Constraints.Infinity,
                    minHeight = 0,
                    maxHeight = Constraints.Infinity
                )
            )
        }

        val hasBoundedWidth = constraints.hasBoundedWidth
        val hasFixedWidth = constraints.hasFixedWidth

        val hasBoundedHeight = constraints.hasBoundedHeight
        val hasFixedHeight = constraints.hasFixedHeight

        val width =
            if (hasBoundedWidth && hasFixedWidth) constraints.maxWidth
            else placeablesWidth.sumOf { it.width }.coerceAtMost(constraints.maxWidth)

        val height =
            if (hasBoundedHeight && hasFixedHeight) constraints.maxHeight
            else placeablesWidth.maxOf { it.height }.coerceAtMost(constraints.maxHeight)


        var posX = 0

        layout(width, height) {
            placeablesWidth.forEachIndexed { index, placeable ->
                placeable.placeRelativeWithLayer(posX, 0) {
                    if (index == fullAlphaIndex) {
                        this.alpha = 1f
                    } else {
                        this.alpha = alpha
                    }
                }

                posX += placeable.width
            }
        }

    }

    Layout(
        modifier = modifier,
        content = content,
        measurePolicy = measurePolicy
    )
}

Same usage but this time with Icons

MyLayout(
    modifier = Modifier.drawChecker(),
    alpha = .4f
) {
    Icon(
        imageVector = Icons.Default.NotificationsActive,
        contentDescription = null,
        tint = Color.White,
        modifier = Modifier
            .background(Color.Red, CircleShape)
            .size(100.dp)
            .padding(10.dp)
    )

    Icon(
        imageVector = Icons.Default.NotificationsActive,
        contentDescription = null,
        tint = Color.White,
        modifier = Modifier
            .background(Color.Red, CircleShape)
            .size(100.dp)
            .padding(10.dp)
    )

    Icon(
        imageVector = Icons.Default.NotificationsActive,
        contentDescription = null,
        tint = Color.White,
        modifier = Modifier
            .layoutId("full_alpha")
            .background(Color.Red, CircleShape)
            .size(100.dp)
            .padding(10.dp)
    )

    Icon(
        imageVector = Icons.Default.NotificationsActive,
        contentDescription = null,
        tint = Color.White,
        modifier = Modifier
            .background(Color.Red, CircleShape)
            .size(100.dp)
            .padding(10.dp)
    )
}

Checker Modifier if anyone wonders is as

fun Modifier.drawChecker() = this.then(
    drawBehind {
        val width = this.size.width
        val height = this.size.height

        val checkerWidth = 10.dp.toPx()
        val checkerHeight = 10.dp.toPx()

        val horizontalSteps = (width / checkerWidth).toInt()
        val verticalSteps = (height / checkerHeight).toInt()

        for (y in 0..verticalSteps) {
            for (x in 0..horizontalSteps) {
                val isGrayTile = ((x + y) % 2 == 1)
                drawRect(
                    color = if (isGrayTile) Color.LightGray else Color.White,
                    topLeft = Offset(x * checkerWidth, y * checkerHeight),
                    size = Size(checkerWidth, checkerHeight)
                )
            }
        }
    }
)
Thracian
  • 43,021
  • 16
  • 133
  • 222
  • Thanks for the answer. Your code works, but if I create a custom `Row Layout` to achieve this, would I lose the benefits of using the builtin `Row` composable like the `verticalAlignment` or `horizontalArrangement` properties? Would I have to implement them myself in the custom `Row` layout? – Raj Narayanan Jan 14 '23 at 19:35
  • Yes, you should. Row and Column are Layouts that use same same MeasurePolicy based on alignment, arrangement and whether if it's Column or Row. You should imeplement inside layout where you place placeables. That determines whether you will have Row or Column with which arrangement and alignment – Thracian Jan 14 '23 at 19:41
  • I have tutorial that explains how layouts, placing, measurables, Constraints, custom modifiers like Box has and more that could help with each step to learn about layouts. You can check it out starting from the section below. https://github.com/SmartToolFactory/Jetpack-Compose-Tutorials#3-2-1-custom-layout – Thracian Jan 14 '23 at 19:42
  • Setting layout width and height based on parent Constraints determines how your Layout will be sized on which size Modifier is assigned or if Modifier.scroll is assigned since scroll changes max constraints to Constrataints.Infinity – Thracian Jan 14 '23 at 19:45
  • Ok. I'll checkout the tutorial. Thanks! – Raj Narayanan Jan 14 '23 at 22:31
  • Thracian, actually, your code in the answer is not working. I can't reproduce the result. It only shows the "custom alpha" `Text` composable at full alpha and does not show the other `Text` composables at all. What needs changed in the code? Thanks. – Raj Narayanan Jan 14 '23 at 22:49
  • It works, setting graphics layer and placing your composables are different things. Depending on how you measure and place them they can overflow from screen or not appear position you want them to be. You need to learn about `Constraints` first to know how Composables behave with each modifier – Thracian Jan 16 '23 at 19:34
  • Thanks for updating your answer. And I'll read your tutorial soon. – Raj Narayanan Jan 17 '23 at 00:11
  • You can check out this answer as well. I think most difficult part with custom layouts is `Constraints`, you can see it by checking out Column or Row source code, measurePolicy. I tried to explain which modifier or scroll changes Constraints as here. https://stackoverflow.com/questions/65779226/android-jetpack-compose-width-height-size-modifier-vs-requiredwidth-requir/73316247#73316247 Constraints section shows how they change with modifiers and vertical scroll – Thracian Jan 17 '23 at 07:17
  • Great. I'll check it out. But, I just want to point out that this line in your answer `Text("Custom Alpha", fontSize = 20.sp), modifier = Modifier.layoutId("full_alpha"))` is throwing an error with a red squiggly line under the `modifier` part which I fixed by removing the extra parenthesis before it. Was it not throwing the error for you? Maybe I'm missing something. Thanks. – Raj Narayanan Jan 17 '23 at 14:35
  • No, you didn't miss anything with `Text("Custom Alpha", fontSize = 20.sp), modifier = Modifier.layoutId("full_alpha"))` it should have been `Text("Custom Alpha", fontSize = 20.sp, modifier = Modifier.layoutId("full_alpha"))` – Thracian Jan 17 '23 at 14:42