Canvas drawing in Compose

Canvas draws points

/**
 *Canvas draw points
 */
@Composable
fun DrawPointsTest() {
    val points = arrayListOf(
        Offset(100f, 100f),
        Offset(300f, 300f),
        Offset(500f, 500f),
        Offset(700f, 700f),
        Offset(900f, 900f)
    )

    Canvas(modifier = Modifier.size(360.dp)) {
        drawPoints(
            points = points,
            //Type PointMode.Points: Points PointMode.Lines: Lines PointMode.Polygon: Polygons
            pointMode = PointMode.Points,
            //color
            color = Color.Blue,
            //color gradient
// brush = Brush.linearGradient(
// 0.0f to Color.Red,
// 0.5f to Color.Green,
// 1.0f to Color.Blue
// ),
            //width
            strokeWidth = 30f,
            //Butt indicates that the starting point and end point of the end contour of the line segment have gentle edges and no extension;
            //Round represents the outline of the line segment starting and ending with a semicircle;
            //Square means that the end of the line segment extends each outline by half the stroke width.
            cap = StrokeCap.Round
        )
    }
}

Use drawPoints to draw points under the Canvas component; add point coordinates with the points attribute; pointMode setting type: PointMode.Points coordinates are points.

PointMode.Lines Two coordinate points form a line segment. If there are not enough coordinates, the last coordinate point will be automatically omitted and not displayed.

PointMode.Polygon polygon, multi-coordinate point connection, is similar to drawPath for drawing paths.

The color attribute sets the color of the coordinate point.

The brush attribute sets the gradient of the coordinate point or line.

The strokeWidth property sets the width of the coordinate point or line.

cap sets whether the end of the coordinate point or line is rounded. If not set, the default is a square coordinate point, and after setting, it is a circular coordinate point. Do not set the actual points or lines.

Canvas draws lines

/**
 * Canvas draws lines
 */
@Composable
fun DrawLineTest() {
    val start = Offset(100f, 100f)
    val end = Offset(900f, 900f)

    Canvas(modifier = Modifier.size(360.dp)) {
        drawLine(
            //line color
            color = Color.Red,
            //starting point
            start = start,
            //end point
            end = end,
            //Line width
            strokeWidth = 30f,
            //Round end of line
            cap = StrokeCap.Round
        )
    }
}

Use drawLine to draw a line. Except for the difference in the coordinate point attributes of the start and end starting points, other attributes are basically the same as the drawing point attributes.

Canvas draws rectangle

/**
 * Canvas draws rectangle (solid)
 */
@Composable
fun DrawRectTest() {
    val topLeft = Offset(100f, 100f)
    Canvas(modifier = Modifier.size(360.dp)) {
        drawRect(
            color = Color.Red,
            topLeft = topLeft,
            size = Size(400f, 600f)
        )
    }
}

/**
 * Canvas draws rectangle (hollow)
 */
@Composable
fun DrawRectStrokTest() {
    val topLeft = Offset(100f, 100f)
    val rectSize = Size(400f, 600f)
    Canvas(modifier = Modifier.size(360.dp)) {
        drawRect(
            color = Color.Red,
            topLeft = topLeft,
            size = rectSize,
            //Stroke set hollow
            style = Stroke(
                //border width
                width = 30f,
                //Used to set the processing method for connecting straight lines and curve segments on the stroke path
                //StrokeJoin.Miter: sharp corner StrokeJoin.Bevel: beveled corner StrokeJoin.Round: rounded corner
                join = StrokeJoin.Round
            )
        )
    }
}

The topLeft attribute sets the top and left margin, and the size attribute sets the size of the rectangle.

The style attribute sets the rectangle style, Stroke is set to a hollow rectangle, the width attribute in Stroken sets the width of the rectangular line, and the join attribute sets the shape of the border corner.

When join is set to StrokeJoin.Bevel, the rectangular border has bevel corners like this:

Not set or set to StrokeJoin.Miter as right angle.

Canvas draws rounded rectangle

/**
 * Canvas draws rounded rectangle
 */
@Composable
fun DrawRoundRectTest() {
    val topLeft = Offset(100f, 100f)
    val rectSize = Size(400f, 600f)
    Canvas(modifier = Modifier.size(360.dp)) {
        drawRoundRect(
            color = Color.Red,
            topLeft = topLeft,
            size = rectSize,
            //round corner settings
            cornerRadius = CornerRadius(100f),
            //Stroke set hollow
            style = Stroke(
                //border width
                width = 30f,
                //Used to set the processing method for connecting straight lines and curve segments on the stroke path
                //StrokeJoin.Miter: sharp corner StrokeJoin.Bevel: beveled corner StrokeJoin.Round: rounded corner
                join = StrokeJoin.Round
            )
        )
    }
}

Use drawRoundRect to draw a rounded rectangle. When the width and height of the rectangle are the same and the rounded angle is set large enough, the rectangle will become a circle. Use the cornerRadius property to set the rounded corners. Just don’t set the style attribute.

Canvas draws a circle

/**
 * Canvas draws circle
 */
@Composable
fun DrawCircleTest() {
    Canvas(modifier = Modifier.size(360.dp)) {
        drawCircle(
            color = Color.Blue,
            //Centered
            center = center,
            //radius of circle
            radius = 300f,
            //Hollow settings
            style = Stroke(
                width=30f
            )
        )
    }
}

Although drawRoundRect can draw a circle, it needs to be set to have the same width and height of the rectangle and the rounded corners are large enough before it becomes a circle. You can set the circle directly using drawCircle. You only need to set the radius of the circle through the radius attribute.

Canvas draws ellipse

/**
 * Canvas draws ellipse
 */
@Composable
fun DrawOvalTest() {
    val topLeft = Offset(100f, 100f)
    val ovalSize = Size(600f, 800f)
    Canvas(modifier = Modifier.size(360.dp)) {
        drawOval(
            color = Color.Blue,
            //starting point
            topLeft = topLeft,
            //size setting
            size = ovalSize,
            //Hollow settings
            style = Stroke(
                width=30f
            )
        )
    }
}

Use drawOval to draw an ellipse. When the width and height of the ellipse are the same, the ellipse will also become a circle.

Therefore, drawOval, drawCircle, and drawRoundRect under Canvas can all set circles.

Canvas draws arc

/**
 * Canvas draws arc
 */
@Composable
fun DrawAcrTest() {
    val ovalSize = Size(600f, 600f)
    Canvas(modifier = Modifier.size(360.dp)) {
        drawArc(
            color = Color.Blue,
            size = ovalSize,
            //Start angle (starting at 3 o'clock)
            startAngle = 0f,
            //scan angle
            sweepAngle = 100f,
            //center alignment
            useCenter = true,
            //Hollow settings
            style = Stroke(
                width=20f
            )
        )
    }
}

Use drawArc to set the arc. You only need to set the startAngle and sweepAngle starting angles to draw a sector or arc.

When the useCenter property is set to false, an arc is drawn.

Canvas draws pictures

/**
 * Canvas draws pictures
 */
@Composable
fun DrawImageTest() {
    val context = LocalContext.current
    val bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.img)
    val image = bitmap.asImageBitmap()
    Canvas(modifier = Modifier.size(360.dp)) {
        //image - the source image to draw
        //srcOffset - optional offset, representing the offset of the upper left corner of the source image to be drawn, defaulting to the origin of the image
        //srcSize - Optional size of the source image drawn relative to srcOffset, defaults to the width and height of the image
        //dstOffset - Optional offset, representing the upper left offset of the destination for drawing the given image, defaults to the origin of the current translation, starting from the upper left offset of the destination for drawing the image
        //dstSize - Optional size of the drawing target, default is srcSize
        //alpha - the opacity applied to the image from 0.0f to 1.0f, representing fully transparent to fully opaque respectively
        //Style - specifies whether the image is filled or drawn with a rectangular stroke
        //colorFilter - applies a colorFilter to the image when drawing to the destination
        //blendMode - the blending algorithm applied to the target
        //filterQuality - The sampling algorithm to apply to the image when it is scaled and drawn to the destination. Default is FilterQuality. Scaling using bilinear sampling algorithm
        drawImage(
            image = image,
            srcOffset = IntOffset(0, 0),
            srcSize = IntSize(400, 400),
            dstOffset = IntOffset(100, 100),
            dstSize = IntSize(800, 800)
        )
    }
}

Use drawImage to draw pictures under Canvas.

Canvas drawing path

/**
 *Canvas draw path
 */
@Composable
fun DrawPathTest() {
    val path = Path()
    path.moveTo(100f, 100f)
    path.lineTo(100f, 300f)
    path.lineTo(400f, 600f)
    path.lineTo(700f, 300f)
    path.lineTo(700f, 100f)
    path.lineTo(650f, 50f)
    path.lineTo(600f, 50f)
    path.lineTo(400f, 200f)
    path.lineTo(200f, 50f)
    path.lineTo(150f, 50f)
// path.quadraticBezierTo(800f, 700f, 600f, 100f) // Second-order Bezier curve
// path.cubicTo(700f, 200f, 800f, 400f, 100f, 100f) // Third-order Bezier curve
    path.close()
    Canvas(modifier = Modifier.size(360.dp)) {
        drawPath(
            //Path settings
            path = path,
            //Color settings
            color = Color.Red,
            //Hollow settings
            style = Stroke(
                width=10f
            )
        )
    }
}

Use drawPath to draw a path under Canvas, and use the path attribute path coordinates under drawPath. The path can use the quadraticBezierTo attribute to add a second-order Bezier curve, and the cubicTo attribute to add a third-order Bezier curve.

Canvas blending mode

/**
 * Use blending modes
 */
@Composable
fun DrawBlendModeTest() {
    Canvas(modifier = Modifier.fillMaxSize()) {
        //Left arm
        drawRoundRect(
            color = Color(0xFFA5CA39),
            topLeft = Offset(100f, 400f),
            size = Size(120f, 400f),
            //round corner settings
            cornerRadius = CornerRadius(100f)
        )
        //right arm
        drawRoundRect(
            color = Color(0xFFA5CA39),
            topLeft = Offset(780f, 400f),
            size = Size(120f, 400f),
            //round corner settings
            cornerRadius = CornerRadius(100f)
        )
        //Left tentacle
        drawLine(
            //line color
            color = Color(0xFFA5CA39),
            //starting point
            start = Offset(350f, 100f),
            //end point
            end = Offset(400f, 200f),
            //Line width
            strokeWidth = 20f,
            //Round end of line
            cap = StrokeCap.Round
        )
        //Right tentacle
        drawLine(
            //line color
            color = Color(0xFFA5CA39),
            //starting point
            start = Offset(650f, 100f),
            //end point
            end = Offset(600f, 200f),
            //Line width
            strokeWidth = 20f,
            //Round end of line
            cap = StrokeCap.Round
        )
        //head
        drawArc(
            color = Color(0xFFA5CA39),
            size = Size(500f, 500f),
            topLeft = Offset(250f, 150f),
            //Start angle
            startAngle = 180f,
            //scan angle
            sweepAngle = 180f,
            //center alignment
            useCenter = true
        )
        //left eye
        drawPoints(
            points = arrayListOf(
                Offset(380f, 280f)
            ),
            pointMode = PointMode.Points,
            color = Color(0xFFFFFFFF),
            strokeWidth = 50f,
            cap = StrokeCap.Round
        )
        //right eye
        drawPoints(
            points = arrayListOf(
                Offset(620f, 280f)
            ),
            pointMode = PointMode.Points,
            color = Color(0xFFFFFFFF),
            strokeWidth = 50f,
            cap = StrokeCap.Round
        )
        //Body
        drawPath(
            path = Path().apply {
                addRoundRect(
                    RoundRect(
                        rect = Rect(
                            offset = Offset(250f, 420f),
                            size = Size(500f, 480f),
                        ),
                        bottomLeft = CornerRadius(100f, 100f),
                        bottomRight = CornerRadius(100f, 100f),
                    )
                )
            },
            color = Color(0xFFA5CA39)
        )
        //left leg
        drawPath(
            path = Path().apply {
                addRoundRect(
                    RoundRect(
                        rect = Rect(
                            offset = Offset(350f, 880f),
                            size = Size(120f, 240f),
                        ),
                        bottomLeft = CornerRadius(100f, 100f),
                        bottomRight = CornerRadius(100f, 100f),
                    )
                )
            },
            color = Color(0xFFA5CA39)
        )
        //right leg
        drawPath(
            path = Path().apply {
                addRoundRect(
                    RoundRect(
                        rect = Rect(
                            offset = Offset(550f, 880f),
                            size = Size(120f, 240f),
                        ),
                        bottomLeft = CornerRadius(100f, 100f),
                        bottomRight = CornerRadius(100f, 100f),
                    )
                )
            },
            color = Color(0xFFA5CA39)
        )
    }
}

What I use here is drawRoundRect to draw a rounded rectangle to draw the arms, drawLine to draw a line to draw the tentacles, use drawArc to draw an arc to draw the head, use drawPoints to draw points, and drawPath to draw the eyes. The body and legs are drawn in the form of paths.

Of course, there is no unique way to draw various shapes. You are welcome to explore more simple and practical Compose drawing methods.