Jetpack Compose place composable with bigger size inside another composable - kotlin

so I want to have a Box as a window with another box in it which is bigger than the outermost Box.
Then I want to have the inner Box draggable, so I can move it around inside the outermost box.
My Code looks like this:
Box(modifier = Modifier
.size(100f.dp, 100f.dp)
.border(2f.dp, Color.Black, RectangleShape)
.clipToBounds()
){
val offsetX = remember { mutableStateOf(0f) }
val offsetY = remember { mutableStateOf(0f) }
Box(modifier = Modifier
.offset(offsetX.value.dp, offsetY.value.dp)
.size(600f.dp, 700f.dp)
.background(Color.Blue) //DEBUG
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consumeAllChanges()
offsetX.value += dragAmount.x
offsetY.value += dragAmount.y
}
}
){
//Contents of inner box
}
}
The problem is now, that the inner box is that the inner box is still only as big as the outer box.
So I can drag the inner box around but see that the background of the inner blue box is only as wide and high as the outer box.
Is there another modifier to use, wo enable bigger composables inside other smaller composables?

After a (long) bit of trying (I hate GUI...) here's my solution:
With .wrapContentSize(unbounded = true) set in the parent composable, the child composable can have a bigger size.
But make sure to set this modifier after .clipToBounds because otherwise the content won't clip as wished

Related

Jetpack compose: Top App Bar pinnedScrollBehavior not changing color on scroll from text focus events

Okay, so this is a bit of a specific issue, but hopefully someone understands what's happening:
I'm using a Jetpack Compose Material 3 CenterAlignedTopAppBar, with TopAppBarDefaults.pinnedScrollBehavior to make it so that the color of the appbar changes when I scroll down on nested content.
It works! In most cases. However, one of my screens has a large text field, that when clicked causes the content to scroll down by itself (to focus on the text field)—and that seems to confuse the appbar, which doesn't change color. It will only change color if I manually scroll up and down.
Relevant code:
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
contentWindowInsets = EmptyWindowInsets,
modifier = Modifier
.fillMaxHeight()
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
// just a wrapper around CenterAlignTopAppBar
StandardTopAppBar(scrollBehavior = scrollBehavior)
},
content = { innerPadding ->
// I've also tried with LazyColumn and see the same behavior
Column(
Modifier
.padding(innerPadding)
.padding(start = 10.dp, end = 10.dp)
.fillMaxHeight()
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
tl;dr; manually scrolling changes the appbar color, but scrolling caused by clicking into a text field that scrolls into view does not. Any idea how I could fix this?
Probably not quite what you are looking for unfortunately, but I had a similar problem. My TopAppBar color was also stuck, but when navigating between different screens. I fixed it by assigning the TopAppBar a different scrollBehaviour depending on the screen.
val scrollBehavior1 = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val scrollBehavior2 = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
In TopAppBar arguments:
scrollBehavior =
when (currentRoute) {
Screens.ExampleScreen1.route -> scrollBehavior1
Screens.ExampleScreen2.route -> scrollBehavior2
else -> null
}
Hopefully this helps you in one way or another.

Animation in Jetpack Compose (AnimatedVisibility) not working at all for me?

I want to animate some text's visibility to not just appear/disappear but to slide in/out in Jetpack Compose Android app that I'm building.
I just literally copy-pasted that little code snippet from developer.android.com/jetpack/compose/animation and it does not work:
var visible by remember { mutableStateOf(true) }
val density = LocalDensity.current
AnimatedVisibility(
visible = visible,
enter = slideInVertically {
// Slide in from 40 dp from the top.
with(density) { -40.dp.roundToPx() }
} + expandVertically(
// Expand from the top.
expandFrom = Alignment.Top
) + fadeIn(
// Fade in with the initial alpha of 0.3f.
initialAlpha = 0.3f
),
exit = slideOutVertically() + shrinkVertically() + fadeOut()
) {
Text("Hello", Modifier.fillMaxWidth().height(200.dp))
}
It simply does not animate, the text gets shown/hidden without any animation.
Any ideas what can be the problem?
I guess I can't paste my whole app here, as it would be silly, it would be nice of Google to give us a Jetpack Compose Playground of sorts, to be able to practice and test code there...

Custom CollapsingTopAppBar Jetpack Compose

The essence of the problem is that I want to write my own version of the AppBar that would include content as another Compose function. After looking at the source code of the current CollapsingTopAppBar implementation, I saw the following lines:
#Composable
private fun TwoRowsTopAppBar(
...
scrollBehavior: TopAppBarScrollBehavior?
) {
...
val pinnedHeightPx: Float = 64.dp
val maxHeightPx: Float = 152.dp
LocalDensity.current.run {
pinnedHeightPx = pinnedHeight.toPx()
maxHeightPx = maxHeight.toPx()
}
// Sets the app bar's height offset limit to hide just the bottom title area and keep top title
// visible when collapsed.
SideEffect {
if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx) {
scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx
}
}
...
Surface(...) {
Column {
TopAppBarLayout(
...
heightPx = pinnedHeightPx
...
)
TopAppBarLayout(
...
heightPx = maxHeightPx - pinnedHeightPx + (scrollBehavior?.state?.heightOffset
?: 0f),
...
)
}
}
}
As I understand it, scrollBehavior is used to handle the collapse and expansion behavior. In the current implementation, just constant values are put in heightOffsetLimit. And since I need my appbar implementation to be able to contain content of any size, I need to somehow know the size of this content in advance and put this value in heightOffsetLimit.
I have already written the code for my AppBar, so that it also contains content. But since I can't pass the height value of the content to scrollBehavior, the AppBar doesn't collapse to the end.
you need to calculate the height that the appbar will have before drawing it into the screen. I have followed this issue and solved my problem with the last solution. hope it helps:
Get height of element Jetpack Compose
use the content you can put (ex. an image or a huge text) as the MainContent
use your appbar as the DependentContent and use the size given in lambda to give the height to your appbar
finally set placeMainContent false as I believe you don't need to draw the image (or any other composable) directly in a box
and you will good to go

Drag Composable only inside given boundries with Jetpack Compose

so I have a black box (rectangle) inside another box (boundary) and the rectangle is set as draggable
But now I can drag the rectangle around the whole window, but I want that if the rectangle "leaves" the boundary it should disappear behind it.
Is there another modifier I can use for it?
Here a bit context:
My code looks like this:
MaterialTheme {
Box(modifier = Modifier
.size(500f.dp)
.border(2f.dp, Color.Black, RectangleShape)
){
Box(modifier = Modifier
.offset { IntOffset(offsetX.roundToInt(), 0) }
.draggable(
orientation = Orientation.Horizontal,
state = rememberDraggableState { delta ->
offsetX += delta
}
)
.background(Color.Blue)
.size(100f.dp)
){
Text("Hello")
}
}
}
When I drag the rectangle outside the boundary it looks like this:
But it should more like this:
On the composable whose contents you want to clip add the clipToBounds() modifier.
Box(modifier = Modifier
.size(500f.dp)
.border(2f.dp, Color.Black, RectangleShape)
.clipToBounds()
)

Divider composable becomes invisible when placed inside composable with horizontalScroll modifier set. Is this a bug?

Background
I am making a desktop compose application.
I had a LazyColumn with Divider separating items. LazyColumn's width may not fit in window so I made the LazyColumn horizontally scrollable by enclosing it inside a Box with horizontalScroll() modifier set.
Now LazyColumn became horizontal scrollable as well. But strangely the Divider's separating the items disappeared.
After digging into it for a while, I figured out that Divider's became invisible only when placed inside a horizontally scrollable parent.
Minimal Reproduction
Here is a minimal reproduction of observed behavior where red Divider is clearly invisible when horizontalScroll(rememberScrollState()) modifier is set in enclosing Box.
fun main() = application {
Window(onCloseRequest = ::exitApplication) {
Box(
Modifier.fillMaxSize()
.background(Color.Green)
.horizontalScroll(rememberScrollState())
) {
Divider(thickness = 10.dp, color = Color.Red)
}
}
}
As it can be seen that the red Divider is invisible for above code.
Expected Output:
With verticalScroll() or no scroll modifier at all works fine as expected.
fun main() = application {
Window(onCloseRequest = ::exitApplication) {
Box(
Modifier.fillMaxSize()
.background(Color.Green)
.verticalScroll(rememberScrollState())
) {
Divider(thickness = 10.dp, color = Color.Red)
}
}
}
Correct output as expected, the red Divider is clearly visible for above code.
Version info
kotlin("jvm") version "1.5.21"
id("org.jetbrains.compose") version "1.0.0-alpha3"
I'd like to know if this is a bug? or is there a way to fix this.
No, this is not a bug.
Divider is used to take the full width, just like any other view with the fillMaxWidth() modifier.
But inside horizontalScroll this modifier is ignored because it is ambiguous: horizontalScroll wraps the size of the content, so the maxWidth constraint in this area is infinite.
From fillMaxWidth documentation:
If the incoming maximum width is Constraints.Infinity this modifier will have no effect.
I haven't found any documentation mentioning horizontalScroll effects Constraints, you can see maintainer's answer on the same issue, or you can test it by yourself like this:
Box {
BoxWithConstraints(Modifier.fillMaxSize()) {
// output 1080 2082
println("non scrollable max dimensions: ${constraints.maxWidth} ${constraints.maxHeight}")
}
BoxWithConstraints(Modifier.fillMaxSize().horizontalScroll(rememberScrollState())) {
// output 2147483647 2082
println("scrollable max dimensions: ${constraints.maxWidth} ${constraints.maxHeight}")
}
}
In this case you need to specify the width explicitly, e.g:
Box(
Modifier
.fillMaxSize()
.background(Color.Green)
.horizontalScroll(rememberScrollState())
) {
var width by remember { mutableStateOf(0) }
val density = LocalDensity.current
val widthDp = remember(width, density) { with(density) { width.toDp() } }
LazyColumn(
modifier = Modifier.onSizeChanged {
width = it.width
}
) {
items(100) {
Text("$it".repeat(it))
Divider(
thickness = 10.dp,
color = Color.Red,
modifier = Modifier.width(widthDp)
)
}
}
}
Note that when the top part of your LazyColumn is smaller than the bottom one, as in my example, the width will be smaller at first (to accommodate only the visible part), but after scrolling down and back up, the width will not be restored to its original width because the Divider width modifier will not let it shrink back down. If you need to prevent this, you need to calculate the `width' based only on the visible elements, which is more complicated.