:large_green_circle::large_blue_square::large_oran...
# compose
r
πŸŸ’πŸŸ¦πŸ”ΆπŸ”Ί*Graphics Shapes APIs Update* We released 1.0.0-alpha04 a week ago, which included bug fixes and significant API changes as we migrated the library to be KMP-friendly (for easier integration with Compose). Briefly: no more Android types. πŸŽ‰ We'd love any feedback you might have on the APIs, we don't plan anymore API changes, so now would be a good time to let us know if you have any issues / suggestions πŸ™‚ The sample code can be found here.
kodee happy 8
πŸ”₯ 13
πŸŽ‰ 5
πŸš€ 11
For a reminder of what this library can unlock for you - morphing between different kinds of shapes! :)
z
No direct feedback, just a Q: Am I right thinking that I can use this to create a squircle shape as well? πŸ˜ƒ
plus1 2
r
If you mean a rounded corner shape? then yes πŸ™‚ You can use RoundedPolygon.Companion.rectangle with the corner rounding. https://developer.android.com/reference/kotlin/androidx/graphics/shapes/RoundedPolygon.Companion#(androidx.graphics.shape[…]tlin.Float,kotlin.Float)
z
Ooh, nice! Ill 99999% likely use this, just gotta find the time to integrate it into my project then πŸ˜ƒ
πŸŽ‰ 3
r
This library can indeed create rounded rectangle with smooth continuity
🀌🏽 1
🀌🏾 1
🀌 1
🀌🏻 1
s
is there a developer guide for this library?
I'm building a play/pause button in Compose UI and would like to morph its shape change from a
CircleShape
to an arbitrary
MaterialTheme.shape.*
value. Can I use
androidx.graphics:graphics-shapes
for this? The demo app is overwhelming to find this answer.
r
s
this is also a wall of text and there's barely any code examples πŸ˜„
r
Fair point 😁
a
I can think of some usecases where this library will be useful, I'll have to try it out!
Quick Q that this library may or may not have the ability to solve… Are we able to change where the path starts for shapes with this library? That way we don’t need to do gymnastics with dash array path effects when manipulating effects like rounded strokes within the library with excessive math…
πŸ‘€ 1
Here's an example, using the shape as the loader, ideally the path would start at the top center naturally
This portion might show some promise at custom shapes, which might help in this, but is there any kind of util being thought of in this case?
r
@saket - good news - some docs that have some more compose centric snippets πŸ™‚ https://developer.android.com/jetpack/compose/graphics/draw/shapes
@andrew its not possible at present, however there is work in progress to change this in future
a
I copied some code from the library and augmented for my use πŸ™‚
s
@Rebecca Franks the code samples look great! πŸŽ‰
a
For context, this screencap should demonstrate why I was asking if we could set where the start point begins πŸ™‚ By default, it'd start to the right which isn't ideal
πŸ‘ 1
z
This works, just curious if theres a better/simpler way? Would be nice seeing adapters like this in the library too if possible 🌟
Copy code
@JvmInline
private value class PolygonShape(
    private val polygon: Density.(Size) -> RoundedPolygon,
) : Shape {

    override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density,
    ): Outline {
        val polygon = with(density) {
            polygon(size)
        }

        val path = createPath(polygon.cubics)

        return Generic(path)
    }

    private fun createPath(
        cubics: List<Cubic>,
        path: Path = Path(),
        scale: Float = 1f,
    ): Path {
        path.rewind()
        cubics.fastFirstOrNull { true }?.let { first ->
            path.moveTo(first.anchor0X * scale, first.anchor0Y * scale)
        }
        cubics.fastForEach { bezier ->
            path.cubicTo(
                bezier.control0X * scale,
                bezier.control0Y * scale,
                bezier.control1X * scale,
                bezier.control1Y * scale,
                bezier.anchor1X * scale,
                bezier.anchor1Y * scale,
            )
        }
        path.close()
        return path
    }
}
r
This looks good - depending if you are remembering the polygon in that lambda somehow? we do have snippets on that page that create a Shape outline, (take in a polygon and scale it appropriately). You'd basically just want to avoid recreating the polygon over and over if possible. These are being thought about for a compose specific set of utils.
z
Thanks for the pointer.. but for whatever reason, when I do it according to the docs .. my shape is just non existent πŸ˜„ Possibly size related? If I remove
matrix.translate(1f, 1f)
then I get a rectangle with the appropiate size.. but its no longer rounded. Code is exactly like the snippet.
r
Whats the size of the area you are using it in? If you can share the snippet of where you've used it, it might help πŸ™‚ also try with just a normal CircleShape, if that doesn't render either then its definitely the size of the area
z
It looks like this, and it works with a regular CircleShape πŸ‘πŸΎ Although surprised that CircleShape ends up being a very rounded rectangle, I thought it would force stuff into a circle, literary!
Copy code
Surface(
        modifier = modifier,
        shape = Theme.structure.squircle, <-- PolygonShape
        elevation = Level2,
        content = {
            Column(
                modifier = modifier.clickable(
                    onClick = onClick,
                ).padding(
                    vertical = KeylineSmall,
                    horizontal = KeylineMedium,
                ),
                content = {
                     ...
                },
            )
        },
r
Looks like we need to update some of the snippets for when sizes aren't correctly passed in. The following snippet should work in the same way as CircleShape does:
Copy code
import androidx.compose.material.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import androidx.graphics.shapes.Cubic
import androidx.graphics.shapes.RoundedPolygon
import androidx.graphics.shapes.circle
import kotlin.math.max

@Preview
@Composable
private fun Preview2() {
    Surface(
        modifier = Modifier,
        shape = RoundedPolygonShape(RoundedPolygon.circle(5)),
        content = {
            Text("Hello")
        }
    )
}

/**
 * Gets a [Path] representation for a [RoundedPolygon] shape, which can be used to draw the
 * polygon.
 *
 * @param path an optional [Path] object which, if supplied, will avoid the function having
 * to create a new [Path] object
 */
@JvmOverloads
fun RoundedPolygon.toPath(path: Path = Path()): Path {
    pathFromCubics(path, cubics)
    return path
}
private fun pathFromCubics(
    path: Path,
    cubics: List<Cubic>
) {
    var first = true
    path.rewind()
    for (element in cubics) {
        if (first) {
            path.moveTo(element.anchor0X, element.anchor0Y)
            first = false
        }
        path.cubicTo(
            element.control0X, element.control0Y, element.control1X, element.control1Y,
            element.anchor1X, element.anchor1Y
        )
    }
    path.close()
}
fun RoundedPolygon.getBounds() = calculateBounds().let { Rect(it[0], it[1], it[2], it[3]) }

class RoundedPolygonShape(
    private val polygon: RoundedPolygon,
    private var matrix: Matrix? = null
) : Shape {
    private val path = Path()
    override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density
    ): Outline {
        path.rewind()
        polygon.toPath(path)
        if (matrix == null) {
            matrix = Matrix()
            val bounds = polygon.getBounds()
            val maxDimension = max(bounds.width, bounds.height)
            matrix!!.scale(size.width / maxDimension, size.height / maxDimension)
            matrix!!.translate(-bounds.left, -<http://bounds.top|bounds.top>)
        }
        path.transform(matrix!!)
        return Outline.Generic(path)
    }
}
z
That works way better. What adjustments would I need to make in order for it to behave like a (rounded) rectangle shape instead of a circle? I am passing in a
RoundedPolygon.Companion.rectangle()
but to no avail, the shape is kind of like an in-between of rectangle and circle. Ive attached a screenshot of the shape Id like to acheive - this is what my earlier code snippet produced, and Im still using the same polygon.
r
Something like
Copy code
RoundedPolygon.rectangle(rounding = CornerRounding(0.5f, smoothing = 1f))
should give you more in line of what you are looking for - the smoothing parameter might help you here