3

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.

2
  • You could use Modifier.rotate on the Composable that shows the image. Commented Jun 8, 2023 at 14:07
  • Modifier.rotate wouldn't be ideal as the Composable needs to show both rotated and non-rotated icons Commented Jun 8, 2023 at 14:23

4 Answers 4

4

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

Sign up to request clarification or add additional context in comments.

1 Comment

Oh wow, this works! Thanks so much! Good to know it's possible to do albeit a bit complicated - Hopefully someone else who runs across this issue in the future will find this answer helpful
3

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

1 Comment

This answer should work for most people - However I can't modify the component I'm passing the resulting VectorPainter to so I'm asking specifically how to rotate the ImageVector itself.
1
+50

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

4 Comments

The composable in question does take a modifier but it would be applied to the entire element and not just the icon - However this does answer my question by confirming there's no way to directly rotate the ImageVector so I'll have to find other ways around it. Thanks!
Not being able to get path from ImageVector is an issue for me in one of my projects as well. I had to resort to create path from operations i posted above. If that's a Composable you own or you can ask for a change you can ask for a rotation param. If that's not the case and you know how big icon inside your Composable and where your IconVector is drawn you can use Modiifer.drawWithContent and transformations to clip icon as it is and draw rotated one above
@Marsroverr There's a way, it just requires a little bit of code. Please, check my answer.
@Thracian it's possible to get the VectorPath data by iterating the root group nodes (VectorGroup is Iterable<VectorNode>)
-4

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

2 Comments

The ImageVector docs don't show such a method: developer.android.com/reference/kotlin/androidx/compose/ui/…
You’re right,, I apologize, you can try use “CompositionLocal” to store the rotation angle, and then use “Transform” composable to apply rotation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.