Rotate ImageVector in Compose

421 Views Asked by At

I'm storing a material icon in a data class like so:

import androidx.compose.ui.graphics.vector.ImageVector

data class Item(
    val icon: ImageVector
)

val item = Item(Icons.Filled.Send)

The item is later passed to a composable where it's drawn using a VectorPainter.

How do I rotate the ImageVector by 90 degrees? Ideally this would result in an ImageVector that I can still store in the data class.

4

There are 4 best solutions below

1
Volo On BEST ANSWER

You can build a rotated ImageVector copying all the groups and paths from the source ImageVector, and applying required rotation parameter (combined with correct values of pivotX/pivotY that determines the center point of rotation).

fun ImageVector.Companion.copyFrom(
    src: ImageVector,
    rotation: Float = src.root.rotation,
    pivotX: Float = src.root.pivotX,
    pivotY: Float = src.root.pivotY,
) = ImageVector.Builder(
    name = src.name,
    defaultWidth = src.defaultWidth,
    defaultHeight = src.defaultHeight,
    viewportWidth = src.viewportWidth,
    viewportHeight = src.viewportHeight,
    tintColor = src.tintColor,
    tintBlendMode = src.tintBlendMode,
    autoMirror = src.autoMirror,
).addGroup(
    src = src.root,
    rotation = rotation,
    pivotX = pivotX,
    pivotY = pivotY,
).build()

private fun ImageVector.Builder.addNode(node: VectorNode) {
    when (node) {
        is VectorGroup -> addGroup(node)
        is VectorPath -> addPath(node)
    }
}

private fun ImageVector.Builder.addGroup(
    src: VectorGroup,
    rotation: Float = src.rotation,
    pivotX: Float = src.pivotX,
    pivotY: Float = src.pivotY,
) = apply {
    group(
        name = src.name,
        rotate = rotation,
        pivotX = pivotX,
        pivotY = pivotY,
        scaleX = src.scaleX,
        scaleY = src.scaleY,
        translationX = src.translationX,
        translationY = src.translationY,
        clipPathData = src.clipPathData,
    ) {
        src.forEach { addNode(it) }
    }
}

private fun ImageVector.Builder.addPath(src: VectorPath) = apply {
    addPath(
        pathData = src.pathData,
        pathFillType = src.pathFillType,
        name = src.name,
        fill = src.fill,
        fillAlpha = src.fillAlpha,
        stroke = src.stroke,
        strokeAlpha = src.strokeAlpha,
        strokeLineWidth = src.strokeLineWidth,
        strokeLineCap = src.strokeLineCap,
        strokeLineJoin = src.strokeLineJoin,
        strokeLineMiter = src.strokeLineMiter,
    )
}

The usage can look like this:

val icon = ImageVector.vectorResource(R.drawable.ic_smile)
val rotatedIcon = ImageVector.copyFrom(
    src = icon,
    rotation = 90f,
    pivotX = icon.defaultWidth.value / 2,
    pivotY = icon.defaultHeight.value / 2,
)

preview

2
999 On

You can use the “rotate” method provided by the “ImageVector” class. E.g:

data class Item(
    val icon: ImageVector
)
val item = Item(Icons.Filled.Send.rotate(90f))
1
Gabriele Mariotti On

You can use the rotate Modifier:

Something like:

Icon(
    Icons.Filled.ArrowDropDown,
    null,
    Modifier.rotate(90f)
)

enter image description here

You can add a condition to achieve both rotated and non-rotated icons.

Something like:

@Composable
fun TrailingIcon(expanded: Boolean) {
    Icon(
        Icons.Filled.ArrowDropDown,
        null,
        Modifier.rotate(if (expanded) 90f else 0f)
    )
}
4
Thracian On

As i get from comments your Composable does not have a modifier param which is not a good design. Every composable even if you won't use it right away, should always have a modifier param.

https://chrisbanes.me/posts/always-provide-a-modifier/

Over the past year or so, I’ve seen lots of composables which look great but they have one fatal flaw: they don’t expose a modifier: Modifier parameter in their signature.

If you don’t want to read the whole post, the TL;DR of this blog post is:

Any composable you write which emits layout (even a simple Box), should have a modifier: Modifier parameter, which is then used in the layout.

Even if the Composable doesn't have a modifier param you can first add rotation param to Item data class

data class Item(
    val icon: ImageVector,
    val rotation: Float
)

Then wrap your Composable that don't have Modifier param with

Box(modifier = Modifier.rotate(item.rotation) {
  // Your Composable here
}

imageVector.root.rotation exposes rotation as immutable param which you cannot change nor exposes Path of IconVector, if that was the cause it would be possible to rotate via matrix.

public val Icons.Filled.Send: ImageVector
    get() {
        if (_send != null) {
            return _send!!
        }
        _send = materialIcon(name = "Filled.Send") {
            materialPath {
                moveTo(2.01f, 21.0f)
                lineTo(23.0f, 12.0f)
                lineTo(2.01f, 3.0f)
                lineTo(2.0f, 10.0f)
                lineToRelative(15.0f, 2.0f)
                lineToRelative(-15.0f, 2.0f)
                close()
            }
        }
        return _send!!
    }

private var _send: ImageVector? = null