In the previous posts of this series we have taken a closer look at how to use shapes and canvases. In this installment I will be focussing on when to draw something. Or why.
Sounds weird? Hang on.
Composables are the building blocks of our user interface. We can either use them as is or amend them with prebuilt functionaly. First, as is:
Text("Hello Compose")
To customize both look and behavior of a composable we use modifier
:
Text(
modifier = Modifier.border(
1.dp,
MaterialTheme.colors.primary
)
.padding(8.dp)
.shadow(4.dp, shape = CircleShape, clip = true),
text = "Hello Compose"
)
As you can see, we have amended, modified, our composable quite a bit by adding a border, a shadow and a padding. There are a lot of prebuilt modifiers. But what if none of these suits your needs? Just roll your own. As you will see shortly, this is very easy with Compose. But let's take a look at the inner workings first.
A modifier is an ordered, immutable collection of modifier elements. androidx.compose.ui.Modifier
is an interface, that helps you define such a collection. then()
returns a Modifier
representing the current modifier followed by another one in sequence. There is also a companion object that implements this interface. It may be used as the start of a modifier extension factory expression. We'll see soon what this means. For now, please take a look at padding()
:
As you can see, then()
receives a PaddingModifier
(a private class inside androidx.compose.foundation.layout.Padding.kt
). Since this affects more layout than drawing I'll not elaborate on this. What's important: this
is another implementation of the Modifier
interface. Which one, depends on what other extension functions have been called before. In my example border()
is invoked first. Now let's tackle this modifier extension factory expression thing. Here's how some lines of border()
look like.
It returns the result of the composed()
modifier, which receives a factory
and a inspectorInfo
.
The factory is both a composable and an extension function of Modifier
. It is wrapped in a ComposedModifier
which in turn is passed to then()
. ComposedModifier
is a private class that extends abstract InspectorValueInfo
and implements Modifier.Element
, a single element contained within a Modifier
chain. But when do you use then()
? And when is a factory preferrable? Fortunately, most of the time you won't need to deal with these questions. That's because there are a few Modifier
extension functions that allow us to draw: drawWithContent()
, drawBehind()
and drawWithCache()
.
fun Modifier.drawOnYellow() = this.drawBehind {
drawRect(Color.Yellow)
}
This modifier draws a yellow rectangle behind the composable that is amended with it.
Text(
modifier = Modifier.drawOnYellow(),
text = "Hello Compose"
)
Your lambda receives an instance of androidx.compose.ui.graphics.drawscope.DrawScope
. This interface defines
a scoped drawing environment with the provided
Canvas
.
This provides a declarative, stateless API to draw shapes
and paths without requiring consumers to maintain
underlyingCanvas
state information.
Next.
drawWithContent()
allows us to draw before or after the layout's contents.
fun Modifier.drawRedCross() = this.drawWithContent {
drawContent()
drawLine(
Color.Red, Offset(0f, 0f),
Offset(size.width - 1, size.height - 1),
blendMode = BlendMode.SrcAtop,
strokeWidth = 8f
)
drawLine(
Color.Red, Offset(0f, size.height - 1),
Offset(size.width - 1, 0f),
blendMode = BlendMode.SrcAtop,
strokeWidth = 8f
)
}
Text(
modifier = Modifier.drawRedCross(),
text = "Imperative"
)
The location of drawContent()
in your lambda defines when the content is drawn. Finally, drawWithCache()
draws into
a
DrawScope
with content that is persisted across
draw calls as long as the size of the drawing area is
the same or any state objects that are read have not
changed. In the event that the drawing area changes, or
the underlying state values that are being read
change, this method is invoked again to recreate objects
to be used during drawing
As you have seen in this post, it is very easy to write extension functions that can modify your composables by drawing on a canvas. Are there other topics related to Canvas
you would like me to cover? Please share your thoughts in the comments.
Top comments (0)