https://kotlinlang.org logo
#compose
Title
# compose
r

Rebecca Franks

12/21/2023, 11:19 AM
🟢🟦🔶🔺*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

Zoltan Demant

12/21/2023, 11:43 AM
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

Rebecca Franks

12/21/2023, 11:58 AM
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

Zoltan Demant

12/21/2023, 12:13 PM
Ooh, nice! Ill 99999% likely use this, just gotta find the time to integrate it into my project then 😃
🎉 3
r

romainguy

12/21/2023, 2:09 PM
This library can indeed create rounded rectangle with smooth continuity
🤌🏽 1
🤌🏾 1
🤌 1
🤌🏻 1
s

saket

12/21/2023, 7:34 PM
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

Rebecca Franks

12/21/2023, 7:56 PM
s

saket

12/21/2023, 8:50 PM
this is also a wall of text and there's barely any code examples 😄
r

Rebecca Franks

12/21/2023, 8:50 PM
Fair point 😁
a

andrew

12/21/2023, 9:31 PM
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

Rebecca Franks

02/09/2024, 3:29 PM
@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

andrew

02/09/2024, 3:36 PM
I copied some code from the library and augmented for my use 🙂
s

saket

02/09/2024, 3:49 PM
@Rebecca Franks the code samples look great! 🎉
a

andrew

02/09/2024, 5:44 PM
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

Zoltan Demant

02/11/2024, 12:20 PM
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

Rebecca Franks

02/12/2024, 11:37 AM
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

Zoltan Demant

02/13/2024, 1:58 PM
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

Rebecca Franks

02/13/2024, 3:22 PM
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

Zoltan Demant

02/13/2024, 3:29 PM
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

Rebecca Franks

02/13/2024, 6:27 PM
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

Zoltan Demant

02/14/2024, 8:20 AM
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

Rebecca Franks

02/14/2024, 3:08 PM
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
53 Views