Jetpack Compose provides a Painter
object to replace the Drawable
APIs used for drawing graphics in Android. A Painter
object represents something that can be drawn and influences the measurement and layout of the composable
it is used in. For example, a BitmapPainter
takes an ImageBitmap
that can draw a Bitmap
on the screen.
A Painter
is different from a DrawModifier
. A DrawModifier
strictly draws within the bounds given to it and does not influence the measurement or layout of the composable
.
To create a custom painter, extend the Painter
class and implement the onDraw
method. This method provides access to the DrawScope
to draw custom graphics. You can also override the intrinsicSize
, which will be used to influence the Composable
that it is contained in.
Below is an example of a custom painter that overlays two images:
class OverlayImagePainter(
private val image: ImageBitmap,
private val imageOverlay: ImageBitmap,
private val srcOffset: IntOffset = IntOffset.Zero,
private val srcSize: IntSize = IntSize(image.width, image.height),
private val overlaySize: IntSize = IntSize(imageOverlay.width, imageOverlay.height),
) : Painter() {
private val size: IntSize = validateSize(srcOffset, srcSize)
override fun DrawScope.onDraw() {
drawImage(
image,
srcOffset,
srcSize,
dstSize = IntSize(
this@onDraw.size.width.roundToInt(),
this@onDraw.size.height.roundToInt(),
),
)
drawImage(
imageOverlay,
srcOffset,
overlaySize,
dstSize = IntSize(
this@onDraw.size.width.roundToInt(),
this@onDraw.size.height.roundToInt()
),
blendMode = BlendMode.Overlay,
)
}
override val intrinsicSize: Size get() = size.toSize()
private fun validateSize(srcOffset: IntOffset, srcSize: IntSize): IntSize {
require(
srcOffset.x >= 0 &&
srcOffset.y >= 0 &&
srcSize.width >= 0 &&
srcSize.height >= 0 &&
srcSize.width <= image.width &&
srcSize.height <= image.height
)
return srcSize
}
}
You can overlay any image on top of your source image with this custom painter as follows:
val rainbowImage = ImageBitmap.imageResource(id = R.drawable.rainbow)
val dogImage = ImageBitmap.imageResource(id = R.drawable.dog)
val customPainter = remember {
OverlayImagePainter(dogImage, rainbowImage)
}
Image(
painter = customPainter,
contentDescription = stringResource(id = R.string.dog_content_description),
contentScale = ContentScale.Crop,
modifier = Modifier.wrapContentSize(),
)
Now, instead of the OverlayImagePainter
example, we will create our own custom painter and draw a new example. In this example, we will create a painter that draws a simple circle with a cross on top of it:
class CrossCirclePainter : Painter() {
override val intrinsicSize: Size = Size(100f, 100f)
override fun DrawScope.onDraw() {
// Circle
drawCircle(
color = Color.Blue,
radius = size.minDimension / 2,
center = center,
)
// Cross
val strokeWidth = 5f
drawLine(
color = Color.Red,
start = Offset(center.x - size.minDimension / 4, center.y - size.minDimension / 4),
end = Offset(center.x + size.minDimension / 4, center.y + size.minDimension / 4),
strokeWidth = strokeWidth,
)
drawLine(
color = Color.Red,
start = Offset(center.x - size.minDimension / 4, center.y + size.minDimension / 4),
end = Offset(center.x + size.minDimension / 4, center.y - size.minDimension / 4),
strokeWidth = strokeWidth,
)
}
}
To use this painter within a Composable:
val customPainter = remember { CrossCirclePainter() }
Image(
painter = customPainter,
contentDescription = null,
modifier = Modifier.size(100.dp),
)
You can also use the custom painter inside a Box
with Modifier.paint(customPainter)
:
val customPainter = remember { CrossCirclePainter() }
Box(
modifier = Modifier
.background(color = Color.Gray)
.padding(30.dp)
.background(color = Color.Yellow)
.paint(customPainter),
) {
// sonar - comment
}
Drawing graphics using a custom painter in Jetpack Compose is quite flexible and powerful. By extending the Painter
class, you can create your own graphics and integrate them into your Composable elements. If you want to make a drawing that influences measurement or layout, you should use the Painter
class. Otherwise, if you only need to draw within the given bounds, using a DrawModifier
would be more appropriate.
You learned how to create custom graphics using two different custom painter examples and how to use these graphics within Composable elements. I hope you found it helpful :)