2

I'm building an image cropper. I'm using rectangle to draw dynamic overlay. When overlay is out of image bounds i move it back to image bounds when pointer is up.

What i build

enter image description here

open var overlayRect: Rect =
    Rect(offset = Offset.Zero, size = Size(size.width.toFloat(), size.height.toFloat()))

and i get final position using this function to move back to valid bounds

internal fun moveIntoBounds(rectBounds: Rect, rectCurrent: Rect): Rect {
    var width = rectCurrent.width
    var height = rectCurrent.height


    if (width > rectBounds.width) {
        width = rectBounds.width
    }

    if (height > rectBounds.height) {
        height = rectBounds.height
    }

    var rect = Rect(offset = rectCurrent.topLeft, size = Size(width, height))

    if (rect.left < rectBounds.left) {
        rect = rect.translate(rectBounds.left - rect.left, 0f)
    }

    if (rect.top < rectBounds.top) {
        rect = rect.translate(0f, rectBounds.top - rect.top)
    }

    if (rect.right > rectBounds.right) {
        rect = rect.translate(rectBounds.right - rect.right, 0f)
    }

    if (rect.bottom > rectBounds.bottom) {
        rect = rect.translate(0f, rectBounds.bottom - rect.bottom)
    }

    return rect
}

And set it on pointer up as

override fun onUp(change: PointerInputChange) {
    touchRegion = TouchRegion.None

    overlayRect = moveIntoBounds(rectBounds, overlayRect)

    // Calculate crop rectangle
    cropRect = calculateRectBounds()
    rectTemp = overlayRect.copy()
}

How can i animate this rect to valid bounds? Is there way to use Animatable to animate a rect?

I checked official document for animation and suggestion is using transition and transition.animateRect from one state to another but i don't have states i want to animate to a dynamic target from current dynamic value and this is a non-Composable class called DynamicCropState that extends a class like zoom state here. Need to animate using Animatable or non-Composable apis.

Thracian
  • 43,021
  • 16
  • 133
  • 222

1 Answers1

0

I solved this creating an AnimationVector4D that converts between Float and Rect by

val RectToVector = TwoWayConverter(
    convertToVector = { rect: Rect ->
        AnimationVector4D(rect.left, rect.top, rect.width, rect.height)
    },
    convertFromVector = { vector: AnimationVector4D ->
        Rect(
            offset = Offset(vector.v1, vector.v2),
            size = Size(vector.v3, vector.v4)
        )
    }
)

For demonstration, created a class to animate internally and return current value of Rect

class RectWrapper {

    private val animatableRect = Animatable(
        Rect(
            offset = Offset.Zero,
            size = Size(300f, 300f)
        ),
        RectToVector
    )

    val rect: Rect
        get() = animatableRect.value

    suspend fun animateRectTo(rect: Rect) {
        animatableRect.animateTo(rect)
    }
}

And a demonstration to show how to use it

@Composable
private fun AnimateRectWithAnimatable() {
    val coroutineScope = rememberCoroutineScope()


    val rectWrapper = remember {
        RectWrapper()
    }

    Column(modifier = Modifier.fillMaxSize()) {

        Button(
            modifier = Modifier
                .padding(10.dp)
                .fillMaxWidth(),
            onClick = {
                coroutineScope.launch {
                    rectWrapper.animateRectTo(
                        Rect(
                            topLeft = Offset(200f, 200f),
                            bottomRight = Offset(800f, 800f)
                        )
                    )
                }
            }
        ) {
            Text("Animate")
        }

        Canvas(
            modifier = Modifier
                .fillMaxSize()
        ) {
            drawRect(
                color = Color.Red,
                topLeft = rectWrapper.rect.topLeft,
                size = rectWrapper.rect.size
            )

        }
    }
}

If you wish to animate a Rect from a class you can implement it as above. I generally pass these classes to modifiers as State and observe and trigger changes inside Modifier.composed and return result to any class that uses that modifier.

Thracian
  • 43,021
  • 16
  • 133
  • 222