https://kotlinlang.org logo
#compose
Title
# compose
z

zoha131

10/19/2020, 1:21 PM
Today I am been trying to implement custom checkable button. I want the button to take all the available places. Unfortunately it only works if I wrap CategoryButton inside a Box. Without Box the text Does not get placed in a wrong way. A little help would be appreciated. Here is my code:
Copy code
import androidx.compose.animation.animate
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.Text
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Layout
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.dp
import androidx.ui.tooling.preview.Preview

@Composable
fun CategoryButton(modifier: Modifier = Modifier) {
    val (checked, setChecked) = remember { mutableStateOf(false) }
    Surface(
        modifier = modifier
            .clip(RoundedCornerShape(4.dp))
            .clickable {
                setChecked(!checked)
            },
        shape = RoundedCornerShape(4.dp)
    ) {
        CategoryButtonLayout(isChecked = checked, modifier = modifier)
    }
}

@Composable
private fun CategoryButtonLayout(isChecked: Boolean, modifier: Modifier) {
    Layout(
        children = { CategoryButtonContent(isChecked) },
        modifier = modifier,
        measureBlock = { miserables, constraints ->
            val width = constraints.maxWidth
            val height = 48.dp.toIntPx()

            val canvasConstraints = Constraints(
                maxHeight = height,
                minHeight = height,
                maxWidth = width,
                minWidth = width
            )

            val iconSize = 24.dp.toIntPx()
            val iconConstraints = Constraints(
                minHeight = iconSize,
                maxHeight = iconSize,
                minWidth = iconSize,
                maxWidth = iconSize
            )

            val canvasPlaceable = miserables[0].measure(canvasConstraints)
            val iconPlaceable = miserables[1].measure(iconConstraints)
            val textPlaceable = miserables[2].measure(constraints)

            layout(width, height) {
                canvasPlaceable.place(0, 0)
                iconPlaceable.place(8, height / 2 - iconPlaceable.height / 2)
                textPlaceable.place(iconSize, height / 2 - textPlaceable.height / 2)
            }
        }
    )
}

@Composable
private fun CategoryButtonContent(isChecked: Boolean) {
    val textColor = if (isChecked) Color.White else Color.Black
    val textColorAnimated = animate(textColor)

    val radius = if (isChecked) 550.dp else 4.dp
    val radiusAnimated = animate(radius)

    Canvas(
        modifier = Modifier.fillMaxSize(),
        onDraw = {

            val left = size.width / 2
            // To move the center of the circle
            translate(left, 0f) {
                drawCircle(color = Color.Red, radius = radiusAnimated.toIntPx().toFloat())
            }

            drawLine(
                color = Color.Red,
                start = Offset(size.width, 0f),
                end = Offset(size.width - 8.dp.toIntPx().toFloat(), 0f),
                strokeWidth = size.width
            )
        }
    )

    Image(
        asset = Icons.Filled.Close,
        colorFilter = ColorFilter.tint(textColorAnimated),
        contentScale = ContentScale.Fit
    )

    Text(
        style = MaterialTheme.typography.button.copy(color = textColorAnimated),
        text = "COVID 19",
        maxLines = 1
    )
}

@Preview
@Composable
fun CategoryButtonPreview() {
    Box(
        Modifier.fillMaxWidth()
    ) {
        CategoryButton()
    }
}

@Preview
@Composable
fun CategoryButtonRowBoxPreview() {
    Row(
        Modifier.fillMaxWidth()
    ) {
        Box(
            Modifier.fillMaxWidth().weight(1f)
        ) {
            CategoryButton()
        }

        Box(
            Modifier.fillMaxWidth().weight(1f)
        ) {
            CategoryButton()
        }
    }
}

@Preview
@Composable
fun CategoryButtonRowPreview() {
    Row(
        Modifier.fillMaxWidth()
    ) {

        CategoryButton(
            modifier = Modifier.fillMaxWidth().weight(1f)
        )

        CategoryButton(
            modifier = Modifier.fillMaxWidth().weight(1f)
        )
    }
}
a

Afzal Najam

10/19/2020, 1:36 PM
Which part is wrong?
z

Zach Klippenstein (he/him) [MOD]

10/19/2020, 1:57 PM
I'm not sure exactly what's wrong, but a few general comments about your code: 1. For things that are "checkable" or toggleable, use the
toggleable
modifier instead of
clickable
- it will give you the correct semantics. 2. For "tight" constraints where min = max, you can use the
Constraints.fixed(width, height)
factory function. 3. In custom layouts, it's better to use
placeRelative
than
place
, so you get automatic RTL support. 4. Shouldn't you be adding 8 to your text x to account for the spacing you give the icon? 5. I don't think it makes sense to use
fillMaxWidth()
and
weight()
together, since they both control width. I believe fillMaxWidth is unnecessary and ends up getting ignored, so I would remove it.
I think the text is wrong for a similar reason to this: https://kotlinlang.slack.com/archives/CJLTWPH7S/p1603102489176000?thread_ts=1603102489.176000&cid=CJLTWPH7S You're measuring the Text with your incoming constraints, which are fixed (min = max) to the half the available total width, and centering itself. What happens if you add either a
textAlign
property to your Text, or a
Modifier.wrapContentWidth(Start)
?
z

zoha131

10/19/2020, 2:41 PM
@Afzal Najam the last row is wrong. Text should be right after the icon. but some how it getting placed to right
@Zach Klippenstein (he/him) [MOD]
Modifier.wrapContentWidth(Start)
worked for me. thanks for help.
2 Views