Drag Composable only inside given boundries with Jetpack Compose - kotlin

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

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...

Jetpack Compose place composable with bigger size inside another composable

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

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.

ImageView is not respecting the size of the CardView

I am trying to make some sort of gallery app. I would like to display images inside of a ScrollView inside a GridLayout so I used CardView for this and it looks like this:
But when I put ImageView inside each CardView, the CardView spreads and fills the whole ScrollView. Here's a preview:
The CardView is created programatically. Here's the code
fun CreateCardView(ItemCount: Int){
for (i in 1 until ItemCount){
/////////////////////CARD VIEW//////////////////////////////
val card_view = CardView(this)
card_view.radius = 8F
card_view.setContentPadding(8,8,8,8)
card_view.setCardBackgroundColor(Color.LTGRAY)
card_view.cardElevation = 8F
val param = GridLayout.LayoutParams()
param.rowSpec = GridLayout.spec(GridLayout.UNDEFINED, 1f)
param.columnSpec = GridLayout.spec(GridLayout.UNDEFINED, 1f)
card_view.layoutParams = param
val param2 = card_view.layoutParams as GridLayout.LayoutParams
param2.setMargins(10,10,10,10)
card_view.layoutParams = param2
subLayoutGrid?.addView(card_view)
////////////////////////END//////////////////////////////////////
///////////////////////IMAGEVIEW////////////////////////////////
val img = ImageView(this)
val imageparams = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT
)
img.layoutParams = imageparams
img.scaleType = ImageView.ScaleType.CENTER_CROP
img.setImageResource(R.drawable.animaleight)
card_view.addView(img)
////////////////////////END///////////////////////////
}
}
How can I put ImageView inside the CardView without filling the whole ScrollView?
I've already fixed my problem. I've tried to use a 40px x 40px Picture then it worked the way I want to. See the image:
It seems like when you use high resolution Picture, the app cant handle the populating of ImageView in GridLayout.