Is there something broken about `Button`s regardin...
# compose
n
Is there something broken about `Button`s regarding their height ? Why won't they "crop" like a
Text
would ? Also, it seems there's a bug when their parent's height is 0.dp (see that blue background still appearing with the parent height being at 0.dp) I'm so confused why would wrapping a
Text
in a
Button
would change so dramatically its behavior when dealing with its height. Am I missing a key concept ? All of this looks like a bug to me (code example in thread) Video example: 1: using
Text
, 2: using
Button
With Text :
Copy code
package fr.delcey.endnewpostcompose

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

class LayoutActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            Box(Modifier.fillMaxSize()) {
                var sliderPosition by remember { mutableFloatStateOf(0f) }
                Column(
                    modifier = Modifier
                        .align(Alignment.TopCenter)
                        .fillMaxWidth()
                        .height((sliderPosition * 200).dp),
                ) {
                    CroppableContent()
                }

                Column(
                    modifier = Modifier
                        .align(Alignment.BottomCenter)
                ) {
                    Slider(
                        value = sliderPosition,
                        onValueChange = { sliderPosition = it }
                    )
                    Text(text = sliderPosition.toString())
                }
            }
        }
    }

    @Composable
    private fun CroppableContent() {
        Text(
            text = "Lorem ipsum dolor sit amet",
            modifier = Modifier
                .fillMaxWidth()
                .height(100.dp)
                .background(Color.Red)
        )

        Text(
            text = "Lorem ipsum dolor sit amet",
            modifier = Modifier
                .fillMaxWidth()
                .height(100.dp)
                .background(Color.Blue)
        )
    }
}
With button :
Copy code
@Composable
    private fun CroppableContent() {
        Button(
            onClick = { /*TODO*/ },
            modifier = Modifier
                .fillMaxWidth()
                .height(100.dp)
                .background(Color.Red)
        ) {
            Text(text = "Lorem ipsum dolor sit amet")
        }
        Button(
            onClick = { /*TODO*/ },
            modifier = Modifier
                .fillMaxWidth()
                .height(100.dp)
                .background(Color.Blue)
        ) {
            Text(text = "Lorem ipsum dolor sit amet")
        }
    }
s
Button is a material component, which follows the material design guidelines and it's for that reason opinionated in how it works and how it lays out itself in different scenarios. If you did it yourself using foundation components, like doing a
Copy code
Surface(Modifier.clickable {}) {
  Text("text", Modifier.padding...)
}
You would experience a more "normal" behavior without that many surprises.
n
So if a component is behaving erratically in "low height", I have no choice but to redo them entirely by myself ? Can't I control this myself with an intermediate layout or a modifier to tell them to "still crop" ? This is just a very simple example and I don't see myself recoding something a Lottie composable just because they don't behave the same way in low height as the defaut components
s
How is Lottie brought up here? I am confused. It's not "erratic" behavior, it's following the material specifications. If your app is not using the material design system and specs, then yeah, it wouldn't make sense that you would expect their components to do what your custom requirements are expecting.
n
This is just a simple example, but the same bugs appear with more complex components like a
LottieAnimation
or a Coil Image. What do you mean by "material design system and specs" ? I just want to make some text / button appear dynamically (by animating their height), I don't feel like I'm breaking the material specs ? I'm so confused about everything here I feel like I'm missing something. I'm not allowed to modify the height of Material Components or their parents ?
s
If you have some specific issue LottieAnimation it might be best to bring that up with their maintainers. Or you can look at how it's done internally to try and see if they make any assumptions for the height of them or whatever your issue is. What I mean for the material components is that hey need to follow the specs as seen here https://m3.material.io/components/buttons/specs so this is then reflected in their internal implementation, for example here https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/[…]dx/compose/material3/Button.kt;l=152-155?q=material3%2FButton If you try to render them with constraints where they would not normally fit, due to how they are implemented the button seems to want to still render itself outside of those incoming constraints, because it has a fixed min height it needs to adhere to. If you can not do what you need to do with those constraints, you need to not use the material component and you can make your own which does not have that
minSize
there for example. Many of the material components do allow you some flexibility in the way they are implemented, but most do not allow you do just anything you might want. The flexibility is limited. You might be interested in this article https://proandroiddev.com/opinion-jetpack-compose-needs-a-design-system-layer-dc579fde79b2. It's not just who you is surprised by these facts. Many people have opinions about this space as a whole.
n
Ok, this is very disappointing that every component will behave differently regarding their height. At least in XML Android this behavior was consistent. No animation about height I guess 😞 Thank you very much for your time, this was very educating. I guess I won't be using Compose for still another couple year πŸ˜„
s
If this is the reason you are not going to use compose, then you will never use compose. This is not some temporary limitation or something unexpected. If you are using components from some design system, then you are bound to the rules of that design system. Compose offers an excellent foundation layer of composables which let you do whatever you wish to do, with as much flexibility as you wish to have. You can also try to be a bit more smart about the height animation and wrap the material button in your column with a AnimatedVisibility which lets you freely edit the animation spec between being invisible and visible. The options are more or less infinite, as long as you don't constraint yourself for no reason.
n
I'm using a back-for-front server with a lot of different components and dynamic height (and many other stuff) I can't control. I can't possibly test all the combinations. I can't rely on a system that behave erratically about the "simplest" of concept (size). That's just visual bugs waiting to happen. And if there's not way to "force back" the "default cropping" (top to bottom) with an intermediate layout or something like that, I can't trust any composable that I didn't do by myself, and it's wayyyy too much of a cost to simply "do Compose" 😞 Thanks for the AnimatedVisibility idea, but it feels like hidding the bad stuff under a rug haha πŸ™ˆ
s
Do as you wish, I still think you are stuck labeling things as "erratic" when I just explained exactly what is going on 🀷 I don't know what else I can add to this conversation.
n
Thank you for your time πŸ™ I'm sorry I don't like Compose, I try really hard but even the simplest thing alway go wrong for the past 4 years 😞
a
Every Material component has a minimum size enforcement by default (to ensure touch target size), which you can disable by this.
Copy code
CompositionLocalProvider(
    LocalMinimumInteractiveComponentEnforcement provides false
) {
    ...
}
But using something like
AnimatedVisibility
is definitely the better choice.
n
That's much better indeed ! Thank you ! But it still behaves like an accordion πŸ‡«πŸ‡· instead of "cropping". How can I achieve that ?
a
Just use
AnimatedVisibility
if you want to animate the appearance. If you really want to control the height yourself, you can apply this to the button.
Copy code
Modifier
    .graphicsLayer(clip = true)
    .wrapContentHeight(align = <http://Alignment.Top|Alignment.Top>, unbounded = true)
πŸ’₯ 1
s
The nuclear option, I like it πŸ˜… Btw in AnimatedVisibility you can also do stuff like
Copy code
AnimatedVisibility(
  visible = ...,
  enter = expandVertically(expandFrom = <http://Alignment.Top|Alignment.Top>),
  exit = shrinkVertically(shrinkTowards = <http://Alignment.Top|Alignment.Top>),
)
you got a lot of control there regarding the animation specs.
n
Yes !!
Copy code
Modifier
    .graphicsLayer(clip = true)
    .wrapContentHeight(align = <http://Alignment.Top|Alignment.Top>, unbounded = true)
is the way to keep the same behavior as XML. Helps me tremendously. Thank you ! The AnimatedVisibility (or
animate*AsState
) doesn't seem to fit my need, as I need sometime to bind the animation to some user gesture, so I need to tight control around the "percentage" of the animation done (and also revert animation, etc)