I am trying to create A UI Components like below
As Part of this i split the task into two , the curved bump and the rectangle.
The curved bump code is below
@Composable
fun TriangularCurve() {
Box(
modifier = Modifier
.fillMaxSize()
.rotate(-90f),
contentAlignment = Alignment.Center
) {
val curvePath = createGaussianPath(0.5f, 0.65f, 0.44f)
Box(
modifier = Modifier
.fillMaxSize()
.background(onboardingSideBarColour, shape = curvePath),
contentAlignment = Alignment.Center
) {
ConcentricCircles(
innerRadius = 39.dp,
outerRadius = 44.dp,
innerColor = onboardingSideBarColour,
outerColor = Color.White
)
Icon(
painter = painterResource(id = R.drawable.arrowdouble),
contentDescription = stringResource(id = R.string.arrow_icon_description),
tint = Color.White,
modifier = Modifier
.size(50.dp)
.rotate(270f)
)
}
}
}
and it looks like this
then there is the rectangular component
@Composable
fun Rectangle() {
Box(
modifier = Modifier
.fillMaxHeight()
.width(20.dp)
.background(onboardingSideBarColour) // Background color for the rectangle
)
}
How can i combine them both side by side like the image at the very top?
You can achieve your goal in two ways:
First Option: Using Two Composables
Create two separate composables (like you did): one for the rectangle and another for the Gaussian curve. Then, combine them in a Row. Since you didn't provide the createGaussianPath()
content, I implemented a basic version using two cubic Bézier curves.
Here’s the code:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
CombineBoxAppTheme {
Row(modifier = Modifier
.padding(4.dp)
.fillMaxSize()) {
CombinedBoxes(
fillColor = Color.Blue,
modifier = Modifier
.fillMaxHeight()
.width(80.dp)
)
Box(modifier = Modifier
.weight(1f)
)
}
}
}
}
}
private fun Path.createGaussianPath(size: Size) {
val width = size.width
val height = size.height
val start = Offset(width, 0f)
val center = Offset(0f, height/2f)
val end = Offset(width, height)
// implement gaussian cure using two cubic bezier curves
val centerDistance = 100f // you can tweak this value to adjust the curvature
val sideDistance = 40f // you can tweak this value to adjust the curvature
val c1 = Offset(width, sideDistance)
val c2 = Offset(0f, center.y - centerDistance)
val c3 = Offset(0f, center.y + centerDistance)
val c4 = Offset(width, end.y - sideDistance)
moveTo(start.x, start.y)
cubicTo(
x1 = c1.x,
y1 = c1.y,
x2 = c2.x,
y2 = c2.y,
x3 = center.x,
y3 = center.y
)
cubicTo(
x1 = c3.x,
y1 = c3.y,
x2 = c4.x,
y2 = c4.y,
x3 = end.x,
y3 = end.y
)
}
@Composable
fun TriangleCurve(
modifier: Modifier = Modifier,
fillColor: Color = Color.Blue,
) {
val shape = remember {
GenericShape { size, _ -> createGaussianPath(size = size) }
}
Box(
modifier = modifier
.size(100.dp)
.background(color = fillColor, shape = shape),
contentAlignment = Alignment.Center
) {
Icon(
painter = painterResource(id = R.drawable.arrowdouble),
contentDescription = null,
tint = Color.White,
modifier = Modifier
.size(40.dp)
.border(color = Color.White, shape = CircleShape, width = 1.dp)
)
}
}
@Preview(showBackground = true)
@Composable
private fun TriangleCurvePreview() {
TriangleCurve(
modifier = Modifier.size(width = 50.dp, height = 200.dp),
fillColor = Color.Blue)
}
@Composable
fun Rectangle(
modifier: Modifier = Modifier,
fillColor: Color = Color.Blue
) {
Box(
modifier = modifier
.fillMaxHeight()
.background(fillColor) // Background color for the rectangle
)
}
@Composable
fun CombinedBoxes(
modifier: Modifier = Modifier,
fillColor: Color = Color.Blue
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
) {
TriangleCurve(
modifier = Modifier.size(width = 50.dp, height = 200.dp),
fillColor = fillColor
)
Rectangle(
modifier = Modifier.width(20.dp),
fillColor = fillColor
)
}
}
Demo:
Second Option: Single Path
The second method, which I recommend, involves creating the entire shape using one path. Here's how to do it:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
CustomBoxAppTheme {
Row(modifier = Modifier.padding(4.dp)) {
SideBar(
modifier = Modifier
.fillMaxHeight()
.width(80.dp)
) {
Icon(
painter = painterResource(id = R.drawable.arrowdouble),
contentDescription = null,
tint = Color.White,
modifier = Modifier
.size(50.dp)
.border(color = Color.White, shape = CircleShape, width = 1.dp)
)
}
Box(modifier = Modifier.weight(1f))
}
}
}
}
}
fun Path.buildSideBarPath(
size: Size,
) {
val width = size.width
val height = size.height
val thickness = width/4f // you can tweak this value to adjust the thickness
val r = width - thickness
val centerY = height/2f
// Move to the top left corner
moveTo(r, 0f)
val start = Offset(r, centerY - r)
val center = Offset(0f, centerY)
val end = Offset(r, centerY + r)
// implement gaussian cure using two cubic bezier curves
val centerDistance = r/2 // you can tweak this value to adjust the curvature
val sideDistance = r/4 // you can tweak this value to adjust the curvature
val c1 = Offset(r, start.y + sideDistance)
val c2 = Offset(0f, center.y - centerDistance)
val c3 = Offset(0f, center.y + centerDistance)
val c4 = Offset(r, end.y - sideDistance)
lineTo(start.x, start.y)
cubicTo(
x1 = c1.x,
y1 = c1.y,
x2 = c2.x,
y2 = c2.y,
x3 = center.x,
y3 = center.y
)
cubicTo(
x1 = c3.x,
y1 = c3.y,
x2 = c4.x,
y2 = c4.y,
x3 = end.x,
y3 = end.y
)
lineTo(r, height)
lineTo(width, height)
lineTo(width, 0f)
close()
}
@Composable
fun SideBar(
modifier: Modifier = Modifier,
fillColor: Color = Color.Blue,
content: @Composable () -> Unit
) {
val shape = remember {
GenericShape { size, _ -> buildSideBarPath(size = size) }
}
Box(
modifier = modifier
.fillMaxSize()
.background(color = fillColor, shape = shape),
contentAlignment = Alignment.Center
) {
content()
}
}
Demo: