Get accustomed!
Recently, a design was passed to me just as a reference for login screen which was a simple screen but has some curves in it. So I ended up trying it just for learning purposes. The screen looked like this
The interesting thing in this is the alignment of the two containers diagonally sort of. So I thought of developing it. Let’s see how it’s done.
So let’s divide the two diagonally separated containers as Signup and Login. We’ll set their shape one by one. Overall both the containers are rectangles with rounded corners except the one diagonally curved corner. Now to create such shape, we can create individual paths for both and provide them with the desired shape.
We’ll create a CurvedShape class that extends Shape class from Composable graphics library. It overrides a function called createOutline in which we’ll provide our custom path for the containers.
class CurvedShape(private val type: CurveType) : Shape { override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { return Outline.Generic( path = if (type == CurveType.LTR) ltrCurve(size) else rtlCurve(size) ) } }
We’ve created an enum for CurveType. The Signup container has LTR (left to right) curve and the Login container has RTL (right to left) curve type. Alright, now we create our path functions for both containers.
LTR Curve Path
In this path we have the bottom right corner connected to bottom left corner diagonally. Let’s name each corner as C1,C2,C3 and C4 starting from top left corner as shown below
Now in our LTR path implementation, we start drawing an arc at C1 at top left corner of the container with a predefined radius. Then we draw a line from C1 to C2. From C2 we create an arc to C3 and from C3 we connect the arc to C4 arc at an angle tilted enough to match our design.
fun ltrCurve(size: Size) = Path().apply { | |
reset() | |
val width = size.width | |
val height = size.height | |
val radius = 100f | |
val upShift = height * (1f - 0.2f) | |
// arc C1 | |
arcTo( | |
rect = Rect( | |
left = 0f, | |
top = 0f, | |
right = radius * 2, | |
bottom = radius * 2 | |
), | |
startAngleDegrees = 180f, | |
sweepAngleDegrees = 90f, | |
forceMoveTo = false | |
) | |
// line C1 to C2 | |
lineTo(width - radius, 0f) | |
// arc C2 | |
arcTo( | |
rect = Rect( | |
left = width - radius * 2, | |
top = 0f, | |
right = width, | |
bottom = radius * 2 | |
), | |
startAngleDegrees = 270f, | |
sweepAngleDegrees = 90f, | |
forceMoveTo = false | |
) | |
// line C2 to C3 | |
lineTo(width, height - (radius * 2)) | |
// arc C3 | |
arcTo( | |
rect = Rect( | |
left = width - radius * 2, | |
top = height - (radius * 2), | |
right = width, | |
bottom = height | |
), | |
startAngleDegrees = 0f, | |
sweepAngleDegrees = 115f, | |
forceMoveTo = false | |
) | |
// arc C4 | |
arcTo( | |
rect = Rect( | |
left = 0f, | |
top = upShift - radius * 2, | |
right = radius * 2, | |
bottom = upShift | |
), | |
startAngleDegrees = 115f, | |
sweepAngleDegrees = 65f, | |
forceMoveTo = false | |
) | |
} |
And we return this path in our CurvedShape createOutline function for LTR type. Finally we set this shape to our Signup Box in the graphics layer.
Box( modifier = modifier .graphicsLayer { shape = CurvedShape(CurveType.LTR) clip = true } .background(Color.Cyan) )
Setting this composable in the main content and running the app will give us the following output
We created our desired sign up shape. We can now add elements inside it and it is ready.
RTL Curve Path
In the same fashion as the LTR curve, we can create our RTL shape. In this case, we’ll again consider four corners as C1, C2, C3 and C4 as shown
Here also, we create C1 arc and join it to C2 and C2 to C3 and finally to C4.
fun rtlCurve(size: Size) = Path().apply { | |
reset() | |
val width = size.width | |
val height = size.height | |
val radius = 100f | |
val upShift = height * (1f - 0.5f) | |
// arc C1 | |
arcTo( | |
rect = Rect( | |
left = 0f, | |
top = 0f, | |
right = radius * 2, | |
bottom = radius * 2 | |
), | |
startAngleDegrees = 180f, | |
sweepAngleDegrees = 110f, | |
forceMoveTo = false | |
) | |
// arc C2 | |
arcTo( | |
rect = Rect( | |
left = width - radius * 2, | |
top = upShift - 10, | |
right = width, | |
bottom = upShift + radius * 2 | |
), | |
startAngleDegrees = -60f, | |
sweepAngleDegrees = 65f, | |
forceMoveTo = false | |
) | |
// arc C3 | |
arcTo( | |
rect = Rect( | |
left = width - radius * 2, | |
top = height - radius * 2, | |
right = width, | |
bottom = height | |
), | |
startAngleDegrees = 0f, | |
sweepAngleDegrees = 90f, | |
forceMoveTo = false | |
) | |
// arc C4 | |
arcTo( | |
rect = Rect( | |
left = 0f, | |
top = height - radius * 2, | |
right = radius * 2, | |
bottom = height | |
), | |
startAngleDegrees = 90f, | |
sweepAngleDegrees = 90f, | |
forceMoveTo = false | |
) | |
} |
Job Offers
And we return this path in our CurvedShape createOutline function for RTL type. Finally we set this shape to our Login Box in the graphics layer.
Box( modifier = modifier .graphicsLayer { shape = CurvedShape(CurveType.RTL) clip = true } .background(Color.Cyan) )
Setting this composable in the main content and running the app will give us the following output
Great!! So we’ve created the desired shapes for our containers. But the main challenge comes now where we need to set them such that the diagonals are aligned to each other correctly. Now setting them in a column will put the Login container below the Signup container’s bottom and they’ll appear as follows
So how will we set them as required? Well! for this we need to create a custom layout modifier which takes x and y coordinates as follows
@Composable fun Modifier.placeAt( x: Int, y: Int, ) = layout { measurables, constraints -> val placeable = measurables.measure(constraints) layout(placeable.width, placeable.height) { placeable.placeRelative(x,y) } }
Now we call this modifier on our Signup and Login containers as follows
val loginY = density.run { 510.dp.toPx() } | |
Box( | |
modifier = Modifier | |
.fillMaxSize() | |
.padding(16.dp), | |
) { | |
Signup( | |
modifier = Modifier | |
.fillMaxWidth() | |
.height(600.dp) | |
.placeAt(0, 0) | |
) | |
Login( | |
modifier = Modifier | |
.fillMaxWidth() | |
.height(250.dp) | |
.placeAt(0, loginY.roundToInt()) | |
) | |
} |
Because the Signup container has a height of 600 dp so we set the top for the Login container as 510 dp which approximately sets well as per our need.
Bamn! We finally created our desired design.
You can check out the github repo and fork it to play around.
Source code: https://github.com/aqua30/CustomLoginDesign
That is all for now.
Until next time…
Cheers!
This article was originally published on proandroiddev.com on June 14, 2022