DEV Community

Alex
Alex

Posted on • Updated on

Custom Composable with Jetpack Compose

Jetpack Compose already offers us a huge library of components to create our UI. But in order to create a more delight and custom experience to the user, we sometimes need to create our own.

In my first article series I want to talk about how to create custom views in Jetpack Compose.

On the example of a Labeled Ranged Slider we go though the steps needed to create a fully custom Composable. I chose this example because the current version of the Range Slider in Compose only offers a Float Range and no easy way to display step aligned labels, changing its appears in accordance to the selected range.

The result of this series will look like this
Labeled Range Slider in motion

We will look at three different topics used to create the Composable and in the end we put it all together.

All the code and projects will be published on GitHub as we go along.

How to draw on a Canvas

Let’s start with looking at how to draw something on a Canvas within a Composable.

As a first simple step we draw a basic shape, like a circle. To do this we

Create our Composable

  • Give it a minimum size
  • Add a Canvas
  • Draw the circle


@Composable
fun DrawSimpleCircle(
   color: Color,
   modifier: Modifier = Modifier,
   minSize: DpSize = DpSize(32.dp, 32.dp)
) {
   Canvas(modifier = modifier.size(minSize)) {
      drawCircle(color)
   }
}


Enter fullscreen mode Exit fullscreen mode

The result of this Composable will be a circle in the given color, filling the size of the whole Composable, e.g.

A simple green circle

Additionally this API allows us to draw our shapes not only with a single color, but also offers a more flexible Brush variant. With a Brush we are able to achieve gradients as well. The example shows a gradient with two colors, but the API allows for a variable list of colors, if desired.



...
drawCircle(
   brush = Brush.radialGradient(
      colors = listOf(
         centerColor, outerColor
      )
   )
)


Enter fullscreen mode Exit fullscreen mode

A circle with radial gradient

A Canvas in Compose can of course not only draw circles. It offers a multitude of different drawing methods. Here are examples of the offers methods and a what it might look like.

drawRect

A simple rect

drawRoundRect

Rect with rounded corners

drawLine

A simple line

drawOval

A simple oval

drawArc

An 180° arc



...
drawArc(
   color = color,
   startAngle = 180f,  // 0f is at 3 o'clock of the circle
   sweepAngle = 180f,  // go 180° from startAngle
   useCenter = false,  // whether to fill the arc or not
   style = Stroke(width = 2.dp.toPx())
)


Enter fullscreen mode Exit fullscreen mode

drawPath

An hour glass drawn with a Path



...
val path = Path().apply {
   lineTo(size.width, 0f)   // line from start to end
   quadraticBezierTo(       // arc from the top to  bottom
      size.width / 2, size.height / 2,
      size.width, size.height
   )
   lineTo(0f, size.height)  // line from end to start
   quadraticBezierTo(       // arc from bottom to top 
      size.width / 2, size.height / 2,
      0f, 0f
   )
   close()                   // link end to start
}

drawPath(
   color = color,
   path = path,
   style = Stroke(width = 2.dp.toPx())
)


Enter fullscreen mode Exit fullscreen mode

drawPoints

Five dots

drawOutline

An outlined rect

drawImage

Showing an image



...
drawImage(
image = image,
dstSize = IntSize(size.width.toInt(), size.width.toInt()),
colorFilter = ColorFilter.lighting(Color.Yellow, Color.Red)
)

Enter fullscreen mode Exit fullscreen mode




Native Canvas

One useful draw function that is not offered directly within the Canvas Composable is the ability to draw a text. But we can still achieve this by using the drawIntoCanvas method. It gives us access to the native Canvas, which offers a drawText method.

Important to note: When using the native Canvas you have to resort to the android.graphics package, using its Paint or Color classes instead of the Compose variants.



Canvas(modifier = modifier.size(minSize)) {
val paint = android.graphics.Paint()
paint.textSize = textSize.toPx()
paint.color = android.graphics.Color.BLACK
drawIntoCanvas {
it.nativeCanvas.drawText(text, 0f, 0f, paint)
}
}

Enter fullscreen mode Exit fullscreen mode




Conclusion

With the Canvas and the native Canvas we are able to draw everything we need for our Labeled Range Slider.

How our final result will look like

In the next part we will take a look at how we can make our custom Composable interactive, by reacting on touch events. Go directly to part 2.

The code for creating the images in this post can be found on GitHub.

Top comments (0)