Graph Editor part 1

Drawing Circle and Square

Now we'll add support to draw the circle and square shapes. First, pass in the colors:

show in full file app/src/main/java/com/androidbyexample/graph/Graph.kt
// ...

@Composable
fun Graph(
    // ...
    shapeOutlineColor: Color = Color.Black,
    triangleColor: Color = Color.Red,
    circleColor: Color = Color.Blue,
    squareColor: Color = Color.Green,
    modifier: Modifier,
) {
    // ...
}
// ...

When drawing a square, we'll need a Size for it. Size is a value class like Offset, so it's a quick calculation, and we won't need a remember. (Using remember would be more expensive.) For the circle, we need to specify its center, which will be an Offset at half the shape size.

show in full file app/src/main/java/com/androidbyexample/graph/Graph.kt
// ...

@Composable
fun Graph(
    // ...
) {
    with(LocalDensity.current) {
        // ...
        val halfShapeSizePx = shapeSizePx/2
        val shapeOutlineWidthPx = shapeOutlineWidthDp.toPx()
        val shapeSize = Size(shapeSizePx, shapeSizePx)
        val halfShapeOffset = Offset(halfShapeSizePx, halfShapeSizePx)
//

        // ...
    }
}
// ...

If we only used these once we could just define them at the usage site, but we'll be using them for every square and circle drawn.

Define drawCircle() and drawSquare() similarly to our drawTriangle. Because DrawScope has drawRect() and drawCircle() functions that we can use, we don't need to define Paths for circle and square.

show in full file app/src/main/java/com/androidbyexample/graph/Graph.kt
// ...

@Composable
fun Graph(
    // ...
) {
    with(LocalDensity.current) {
        // ...
            }
        }
        fun DrawScope.drawSquare(
            offset: Offset,
        ) {
            translate(offset.x, offset.y) {
                drawRect(
                    topLeft = Offset.Zero,
                    size = shapeSize,
                    color = squareColor,
                    style = Fill,
                )
                drawRect(
                    topLeft = Offset.Zero,
                    size = shapeSize,
                    color = shapeOutlineColor,
                    style = shapeOutlineStroke,
                )
            }
        }
        fun DrawScope.drawCircle(
            offset: Offset,
        ) {
            translate(offset.x, offset.y) {
                drawCircle(
                    center = halfShapeOffset,
                    radius = halfShapeSizePx,
                    color = circleColor,
                    style = Fill,
                )
                drawCircle(
                    center = halfShapeOffset,
                    radius = halfShapeSizePx,
                    color = shapeOutlineColor,
                    style = shapeOutlineStroke,
                )
            }
        }

        // ...
    }
}
// ...

Let's add calls in the Canvas:

show in full file app/src/main/java/com/androidbyexample/graph/Graph.kt
// ...

@Composable
fun Graph(
    // ...
) {
    with(LocalDensity.current) {
        // ...
        }

//      val halfShapeOffset = Offset(shapeSizePx/2, shapeSizePx/2)
//
        Canvas(modifier = modifier) {
//          val offset = center - halfShapeOffset
//          drawLine(
//              start = Offset(size.width/2, 0f),
//              end = Offset(size.width/2, size.height),
//              color = Color.DarkGray,
//          )
//          drawLine(
//              start = Offset(0f, size.height/2),
//              end = Offset(size.width, size.height/2),
//              color = Color.DarkGray,
//          )
//          drawTriangle(offset)
            drawTriangle(Offset(100f, 100f))
            drawCircle(Offset(150f, 150f))
            drawSquare(Offset(200f, 200f))
        }
    }
}
// ...

When we run, we'll see

Overlapping shapes

I've intentionally overlapped the shapes to again point out the use of the "painter's algorithm" when drawing. Later-drawn shapes appear on top of earlier-drawn ones. This becomes even more important later...


All code changes

CHANGED: app/src/main/java/com/androidbyexample/graph/Graph.kt
package com.androidbyexample.graph

import androidx.compose.foundation.Canvas
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

@Composable fun Graph( shapeSizeDp: Dp = 48.dp, shapeOutlineWidthDp: Dp = 3.dp, shapeOutlineColor: Color = Color.Black, triangleColor: Color = Color.Red,
circleColor: Color = Color.Blue, squareColor: Color = Color.Green,
modifier: Modifier, ) {
with(LocalDensity.current) { val shapeSizePx = shapeSizeDp.toPx() val halfShapeSizePx = shapeSizePx/2 val shapeOutlineWidthPx = shapeOutlineWidthDp.toPx()
val shapeSize = Size(shapeSizePx, shapeSizePx) val halfShapeOffset = Offset(halfShapeSizePx, halfShapeSizePx) //
val trianglePath = Path().apply { moveTo(shapeSizePx/2, 0f) lineTo(shapeSizePx, shapeSizePx) lineTo(0f, shapeSizePx) close() }
val shapeOutlineStroke = remember(shapeOutlineWidthPx) { Stroke(shapeOutlineWidthPx) } fun DrawScope.drawTriangle( offset: Offset, ) { translate(offset.x, offset.y) { drawPath( path = trianglePath, color = triangleColor, style = Fill, ) drawPath( path = trianglePath, color = shapeOutlineColor, style = shapeOutlineStroke, ) } }
fun DrawScope.drawSquare( offset: Offset, ) { translate(offset.x, offset.y) { drawRect( topLeft = Offset.Zero, size = shapeSize, color = squareColor, style = Fill, ) drawRect( topLeft = Offset.Zero, size = shapeSize, color = shapeOutlineColor, style = shapeOutlineStroke, ) } } fun DrawScope.drawCircle( offset: Offset, ) { translate(offset.x, offset.y) { drawCircle( center = halfShapeOffset, radius = halfShapeSizePx, color = circleColor, style = Fill, ) drawCircle( center = halfShapeOffset, radius = halfShapeSizePx, color = shapeOutlineColor, style = shapeOutlineStroke, ) } }
// val halfShapeOffset = Offset(shapeSizePx/2, shapeSizePx/2) // Canvas(modifier = modifier) { // val offset = center - halfShapeOffset // drawLine( // start = Offset(size.width/2, 0f), // end = Offset(size.width/2, size.height), // color = Color.DarkGray, // ) // drawLine( // start = Offset(0f, size.height/2), // end = Offset(size.width, size.height/2), // color = Color.DarkGray, // ) // drawTriangle(offset) drawTriangle(Offset(100f, 100f)) drawCircle(Offset(150f, 150f)) drawSquare(Offset(200f, 200f)) }
} }