This article talks about Canvas in Compose.
1. Canvas
@Composable
fun Canvas(
modifier: Modifier,
onDraw: DrawScope.() -> Unit
) = Spacer(modifier.drawBehind(onDraw))
modifier
: The main function here is to specify the size of the canvas.onDraw
It is to perform specific drawing. It can be seen that it provides a scope of drawing environmentDrawScope
, where we provide the drawing api and properties we often use, such asdrawLine
, ,size
and so on.
Let's take a simple example to see how to use it:
Canvas(modifier = Modifier.fillMaxSize()) {
val canvasWidth = size.width
val canvasHeight = size.height
drawLine(
start = Offset(x = canvasWidth, y = 0f),
end = Offset(x = 0f, y = canvasHeight),
color = Color.Blue
)
}
Draw a line that starts and ends at the upper-right and lower-left corners of the canvas, respectively. The effect is as follows:
2. Drawing method
1. drawLine
drawLine
It is briefly explained in the above example, of course it is more than these functions.
fun drawLine(
color: Color, //或 brush: Brush,
start: Offset,
end: Offset,
strokeWidth: Float = Stroke.HairlineWidth,
cap: StrokeCap = Stroke.DefaultCap,
pathEffect: PathEffect? = null,
/*FloatRange(from = 0.0, to = 1.0)*/
alpha: Float = 1.0f,
colorFilter: ColorFilter? = null,
blendMode: BlendMode = DefaultBlendMode
)
- Specify the color of the line with
color
- Gradient colors can be used
brush
, as explained in the third article of this series. strokeWidth
Is the width of the line, the default is 1px.cap
It is the shape of the thread head, and the default isStrokeCap.Butt
flat head. AndStrokeCap.Round
round head,StrokeCap.Square
square head. This part is the same as Paint in Android. The difference between a flat head and a square head is whether it is an extended part.
pathEffect
It is the effect of a line segment, such as a dotted line, which is usedPathEffect.dashPathEffect(intervals: FloatArray, phase: Float = 0f)
, for example:
PathEffect.dashPathEffect(floatArrayOf(20f, 10f), 10f)
intervals
20f in represents the width of the dotted line, and 10f is the interval width. phase
The 10f represents the initial offset distance. Therefore, the offset of 10f at the beginning will cause the line segment of the first segment to be "cut" by 10f, and the specific effect is as follows:
alpha
is the transparency of the line segment.colorFilter
It is a color filter. It is explained in the fourth article of this series, so I won’t repeat it here.blendMode
: blend mode. This is beyond the scope of this article, and I will talk about it in detail later when I have a chance. If you are interested, you can read the reference article at the end of the article first.
2. drawRect
The method of drawing a rectangle has the same properties as the method drawLine
, and some differences are mentioned below.
fun drawRect(
color: Color,
topLeft: Offset = Offset.Zero,
size: Size = this.size.offsetSize(topLeft),
/*@FloatRange(from = 0.0, to = 1.0)*/
alpha: Float = 1.0f,
style: DrawStyle = Fill,
colorFilter: ColorFilter? = null,
blendMode: BlendMode = DefaultBlendMode
)
topLeft
It is used to specify the offset of the upper left corner. If not specified, it will start from the upper left corner of the current canvas by default.size
Used to specify the size of the rectangle, if not specified, the default is the size of the current canvas.style
Is solid or hollow. The defaultFill
is solid andStroke
hollow.
3. drawRoundRect
Drawing a rounded rectangle is basically the same as a rectangle, but there is one more parameter to set the size of the rounded corners drawRoundRect
, so I won’t explain it here.
4. drawImage
Draw picture method
fun drawImage(
image: ImageBitmap,
srcOffset: IntOffset = IntOffset.Zero,
srcSize: IntSize = IntSize(image.width, image.height),
dstOffset: IntOffset = IntOffset.Zero,
dstSize: IntSize = srcSize,
/*@FloatRange(from = 0.0, to = 1.0)*/
alpha: Float = 1.0f,
style: DrawStyle = Fill,
colorFilter: ColorFilter? = null,
blendMode: BlendMode = DefaultBlendMode
)
image
: The picture to be drawn, which can beImageBitmap.imageResource(id = R.drawable.xxx)
obtained by using the method.srcOffset
: The offset of the upper left corner of the image that needs to be drawn, which defaults to the origin of the image.srcSize
: The relativesrcOffset
size of the image, the default is the width and height of the image.dstOffset
: The offset relative to the upper left corner of the drawn image, which defaults to the origin of the image.dstSize
: The size of the drawn image, the default issrcSize
.
The following code is to draw the lower right corner area of a picture, offset 50 * 50 relative to the canvas, and the drawn size is 200 * 200.
val imageBitmap = ImageBitmap.imageResource(id = R.mipmap.ic_launcher)
Canvas(modifier = Modifier.fillMaxSize()) {
drawImage(
image = imageBitmap,
srcOffset = IntOffset(imageBitmap.width / 2,imageBitmap.height / 2),
srcSize = IntSize(imageBitmap.width, imageBitmap.height),
dstOffset = IntOffset(50,50),
dstSize = IntSize(200,200)
)
}
The effect is as follows:
5. drawCircle
draw circle method
fun drawCircle(
color: Color,
radius: Float = size.minDimension / 2.0f,
center: Offset = this.center,
/*@FloatRange(from = 0.0, to = 1.0)*/
alpha: Float = 1.0f,
style: DrawStyle = Fill,
colorFilter: ColorFilter? = null,
blendMode: BlendMode = DefaultBlendMode
)
radius
: The radius of the circle.center
: The position of the center of the circle.
6. drawArc
drawArc
Can be used to draw arcs or sectors
fun drawArc(
color: Color,
startAngle: Float,
sweepAngle: Float,
useCenter: Boolean,
topLeft: Offset = Offset.Zero,
size: Size = this.size.offsetSize(topLeft),
/*@FloatRange(from = 0.0, to = 1.0)*/
alpha: Float = 1.0f,
style: DrawStyle = Fill,
colorFilter: ColorFilter? = null,
blendMode: BlendMode = DefaultBlendMode
)
startAngle
: start anglesweepAngle
: Angle swept by the arcuseCenter
: Whether the arc passes through the center of the circle
I won’t give an example here, you can use style
and useCenter
attribute to combine and try.
7. drawPath
draw path method
fun drawPath(
path: Path,
color: Color,
/*@FloatRange(from = 0.0, to = 1.0)*/
alpha: Float = 1.0f,
style: DrawStyle = Fill,
colorFilter: ColorFilter? = null,
blendMode: BlendMode = DefaultBlendMode
)
In fact, it is the same as the use of path in Android, and the methods of moveTo, lineTo, and close will not be described in detail.
8. drawPoints
method of plotting points
fun drawPoints(
points: List<Offset>,
pointMode: PointMode,
color: Color,
strokeWidth: Float = Stroke.HairlineWidth,
cap: StrokeCap = StrokeCap.Butt,
pathEffect: PathEffect? = null,
/*@FloatRange(from = 0.0, to = 1.0)*/
alpha: Float = 1.0f,
colorFilter: ColorFilter? = null,
blendMode: BlendMode = DefaultBlendMode
)
points
: the offset position of the pointpointMode
: Point drawing mode,PointMode.Points
draw each point separately.PointMode.Lines
Every two points are drawn as a line segment. If the number of points is odd, the last point is ignored.PointMode.Polygon
Connect all the dots.
Finally, there is another method of drawing an ellipse drawOval
. The usage is similar, so I won’t explain it here.
3. DrawScope extension method
1. inset
At the same time transform DrawScope
the coordinate space from left to top and modify the size of the current drawing area.
inline fun DrawScope.inset(
left: Float,
top: Float,
right: Float,
bottom: Float,
block: DrawScope.() -> Unit
) {
...}
inset
It's a bit like embedding a "new" canvas on the original canvas, and the left and top are set to be the corresponding padding.
Canvas(modifier = Modifier.fillMaxSize()){
drawRect(
color = Color.Blue,
)
inset(100f, 100f, 100f, 100f) {
drawRect(
color = Color.Red,
)
}
}
2. translate
Pan the drawing area
inline fun DrawScope.translate(
left: Float = 0.0f,
top: Float = 0.0f,
block: DrawScope.() -> Unit
) {
...}
You only need to set the moving distance in the left and top directions.
3. rotate、rotateRad
Rotate drawing area
inline fun DrawScope.rotate(
degrees: Float,
pivot: Offset = center,
block: DrawScope.() -> Unit
) {
...}
inline fun DrawScope.rotateRad(
radians: Float,
pivot: Offset = center,
block: DrawScope.() -> Unit
) {
...}
degrees
is the angle of rotation.radians
is how many radians to rotate.pivot
is the center point of the rotation, the default is the center.
4. scale
Zoom the drawing area.
inline fun DrawScope.scale(
scaleX: Float,
scaleY: Float,
pivot: Offset = center,
block: DrawScope.() -> Unit
) {
...}
Just specify the zoom factor in the x and y directions.
5. clipRect
crop the given rectangular area
inline fun DrawScope.clipRect(
left: Float = 0.0f,
top: Float = 0.0f,
right: Float = size.width,
bottom: Float = size.height,
clipOp: ClipOp = ClipOp.Intersect,
block: DrawScope.() -> Unit
) {
...}
clipOp
:ClipOp.Intersect
It is the inside of the clipping rectangle andClipOp.Difference
the outside of the clipping rectangle.
Let's look at a simple example for your understanding:
Canvas(modifier = Modifier.fillMaxSize()){
drawRect(
color = Color.Blue,
)
clipRect(200f, 200f, clipOp = ClipOp.Intersect) {
drawRect(
color = Color.Yellow,
)
}
}
left is ClipOp.Intersect
, right isClipOp.Difference
clipPath
the same way.
6. drawIntoCanvas
You can directly call the underlying Canvas drawing method. We use it to implement the drawLine example at the beginning, drawing a diagonal line:
Canvas(modifier = Modifier.fillMaxSize()) {
val canvasWidth = size.width
val canvasHeight = size.height
drawIntoCanvas {
val paint = Paint()
paint.color = Color.Blue
paint.strokeWidth = 1f
it.drawLine(
p1 = Offset(canvasWidth,0f),
p2 = Offset(0f,canvasHeight),
paint = paint
)
}
}
Among them drawLine
, the method is not DrawScope
in the beginning drawLine
:
actual typealias NativeCanvas = android.graphics.Canvas
private val EmptyCanvas = android.graphics.Canvas()
@PublishedApi internal class AndroidCanvas() : Canvas {
@PublishedApi internal var internalCanvas: NativeCanvas = EmptyCanvas
override fun drawLine(p1: Offset, p2: Offset, paint: Paint) {
internalCanvas.drawLine(
p1.x,
p1.y,
p2.x,
p2.y,
paint.asFrameworkPaint()
)
}
}
You can see that Android's Canvas api is finally called.
7. withTransform
Perform 1 or more transformations. That is, the above translation and rotation can be performed together.
inline fun DrawScope.withTransform(
transformBlock: DrawTransform.() -> Unit,
drawBlock: DrawScope.() -> Unit
) {
...}
This is the end of this article, the next article should be the animation part of compose, so stay tuned~