Blog Infos
Author
Published
Topics
Published

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
)
}
view raw LTRFun.kt hosted with ❤ by GitHub

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
)
}
view raw RTLFun.kt hosted with ❤ by GitHub

Job Offers

Job Offers


    Talent Acquisition Manager – Technology

    FanDuel
    New York, NY; Atlanta, GA
    • Full Time
    apply now

    Senior Android Engineer

    Busuu
    Madrid
    • Full Time
    apply now

    Android Engineer

    American Express
    Phoenix, USA
    • Full Time
    apply now
Load more listings

OUR VIDEO RECOMMENDATION

, ,

What does Recomposition mean to your app?

You’ve heard a lot that Jetpack Compose is a declarative UI toolkit and it recomposes only the components that changed. But what does it exactly mean? How does it apply not only in the scale…
Watch Video

What does Recomposition mean to your app?

Aida Issayeva
Senior Software Engineer
Android

What does Recomposition mean to your app?

Aida Issayeva
Senior Software Engi ...
Android

What does Recomposition mean to your app?

Aida Issayeva
Senior Software Engineer
Android

Jobs

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

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE
blog
Yes! You heard it right. We’ll try to understand the complete OTP (one time…
READ MORE

Leave a Reply

Your email address will not be published.

Fill out this field
Fill out this field
Please enter a valid email address.

Menu