Reputation: 66599
In jetpack Compose when you use Layout
or SubcomposeLayout
you measure your Composable with the Constraints
limits.
If there is a fixed size Modifier, Modifier.size(500.dp), let's say 1000x1000 in Int, if any child Composable needs to have 1100x1000px size, layout
function should be as
layout(1100, 1100){
}
since Constraints
has maxWidth=1000, and maxHeight=1000 as max limits when Modifier.size() or Modifier.sizeIn(maxWidth, maxHeight) used Composable breaks and jumps as difference between size from Modifier
and the one you assign to layout function above.
Because of this i need to increase size of Modifier
before i set it to
SubcomposeLayout(
modifier = modifier
) { constraints ->
layout(width, height) {
}
}
Is this possible with any Modifier
such as Modifier.increseSize(15.dp)
? I know there is no such Modifier but what i need is increasing size of a Modifier before setting it to a Layout
What i do is a Scalable SubcomposeLayout. First gets size of its main content(Image) then adds handle size to content size and sets layout(width,height)
function total size as sum of handle and content which is bigger than content size or Modifier.size()
, this works when Constraints don't return a bounded upper width or height to limit measurement.
Total layout size must be bigger than content size because touchable region should cover handles' every quarter for to be clickable as you can see in the image even outside the content area handles receive gestures.
@Composable
internal fun TransformSubcomposeLayout(
modifier: Modifier = Modifier,
handleRadius: Dp = 15.dp,
mainContent: @Composable () -> Unit,
dependentContent: @Composable (IntSize) -> Unit
) {
val handleRadiusInPx = with(LocalDensity.current) {
handleRadius.roundToPx()
}
SubcomposeLayout(
modifier = modifier
) { constraints ->
// Subcompose(compose only a section) main content and get Placeable
val mainPlaceables: List<Placeable> = subcompose(SlotsEnum.Main, mainContent)
.map {
it.measure(constraints)
}
// Get max width and height of main component
val maxSize =
mainPlaceables.fold(IntSize.Zero) { currentMax: IntSize, placeable: Placeable ->
IntSize(
width = maxOf(currentMax.width, placeable.width),
height = maxOf(currentMax.height, placeable.height)
)
}
// Set sum of content and handle size as total size of this Composable
val width = maxSize.width + 2 * handleRadiusInPx
val height = maxSize.height + 2 * handleRadiusInPx
val dependentPlaceables = subcompose(SlotsEnum.Dependent) {
dependentContent(maxSize)
}.map {
it.measure(constraints)
}
layout(width, height) {
dependentPlaceables.forEach { placeable: Placeable ->
// place Placeables inside content area by offsetting by handle radius
placeable.placeRelative(
(width - placeable.width) / 2,
(height - placeable.height) / 2
)
}
}
}
}
enum class SlotsEnum { Main, Dependent }
And i pass the content size so i can size the contents
@Composable
fun TransformLayout(
modifier: Modifier = Modifier,
enabled: Boolean = true,
handleRadius: Dp = 15.dp,
handlePlacement: HandlePlacement = HandlePlacement.Corner,
onDown: (Transform) -> Unit = {},
onMove: (Transform) -> Unit = {},
onUp: (Transform) -> Unit = {},
content: @Composable () -> Unit
) {
TransformSubcomposeLayout(
// 🔥 !!! I'm not able to pass modifier from function because if it contains
// a Modifier.size() it breaks layout() function
// and because of that i can't set border, zIndex or any other Modifier from
// user. ZIndex must be set here to work, since it works against siblings
modifier = Modifier.border(3.dp, Color.Green),
handleRadius = handleRadius.coerceAtLeast(12.dp),
mainContent = {
// 🔥 Modifier from user is only used for measuring size
Box(
modifier = modifier,
contentAlignment = Alignment.Center
) {
content()
}
},
dependentContent = { intSize: IntSize ->
val dpSize = with(LocalDensity.current) {
val rawWidth = intSize.width.toDp()
val rawHeight = intSize.height.toDp()
DpSize(rawWidth, rawHeight)
}
TransformLayoutImpl(
enabled = enabled,
handleRadius = handleRadius,
dpSize = dpSize,
handlePlacement = handlePlacement,
onDown = onDown,
onMove = onMove,
onUp = onUp,
content = content
)
}
)
}
I want to use this layout as
val density = LocalDensity.current
val size = (500 / density.density).dp
TransformLayout(
modifier = Modifier.size(size).border(5.dp, Color.Red).zIndex(100f),
enabled = enabled,
handlePlacement = HandlePlacement.Side
) {
Image(
painter = painterResource(id = R.drawable.landscape1),
contentScale = ContentScale.FillBounds,
contentDescription = "",
)
}
If i set layout(500+, 500+) when this modifier size is 500x500 and set it as SubcomposeLayout Modifier Composable is not placed in correct position.
Upvotes: 2
Views: 3196
Reputation: 66599
Changing Modifier size maybe possible with creating a Modifier such as SizeModifier
. If anyone posts accurate answer that accomplishes task that way would be more than welcome.
I solved this by first setting a requiredSize to have minimum dimensions for this Composable.
@Composable
fun TransformLayout(
modifier: Modifier = Modifier,
enabled: Boolean = true,
handleRadius: Dp = 15.dp,
handlePlacement: HandlePlacement = HandlePlacement.Corner,
onDown: (Transform) -> Unit = {},
onMove: (Transform) -> Unit = {},
onUp: (Transform) -> Unit = {},
content: @Composable () -> Unit
) {
MorphSubcomposeLayout(
modifier = modifier
.requiredSizeIn(
minWidth = handleRadius * 2,
minHeight = handleRadius * 2
)
}
In SubcomposeLayout
instead of increasing size of Composable by handles which doesn't work with Modifier.size
I constrained maximum dimensions to maxWidth and maxHeight of constraints with
// Get max width and height of main component
var maxWidth = 0
var maxHeight = 0
mainPlaceables.forEach { placeable: Placeable ->
maxWidth += placeable.width
maxHeight = placeable.height
}
val handleSize = handleRadiusInPx * 2
maxWidth = maxWidth.coerceAtMost(constraints.maxWidth - handleSize)
maxHeight = maxHeight.coerceAtMost(constraints.maxHeight - handleSize)
val maxSize = IntSize(maxWidth, maxHeight)
And passed with subcompose()
function to dependent Composable as dimensions of content
val dependentPlaceables = subcompose(SlotsEnum.Dependent) {
dependentContent(maxSize)
}.map {
it.measure(constraints)
}
When i set dimensions of parent Composable with layout(width, height)
added the handle size or area i subtract initially
width = maxSize.width + 2 * handleRadiusInPx
height = maxSize.height + 2 * handleRadiusInPx
layout(width, height) {
dependentPlaceables.forEach { placeable: Placeable ->
placeable.placeRelative(0, 0)
}
}
This way i'm able to shrink content area by handle size when there is a fixed size modifier, if there is no fixed size modifier to limit measurement with upper bound as big as Composable it works as in question.
Upvotes: 2