Raj Narayanan
Raj Narayanan

Reputation: 3432

How to exclude a child composable from parent alpha value change?

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",
                )
            }
        }
    }
}

Upvotes: 0

Views: 881

Answers (1)

Thracian
Thracian

Reputation: 67149

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)
                )
            }
        }
    }
)

Upvotes: 1

Related Questions