● Google Developer Expert for Android/Kotlin
● Android @ Clue
● Open Source contributor
● Speaker
● And coffee maker while gradle builds
hi_man_shoe
Himanshu Singh
About Me
2
Slide 3
Slide 3 text
Composing in
your Canvas
Maths 101.
hi_man_shoe
Slide 4
Slide 4 text
Talk Agenda
1.
2.
3.
4.
5.
Canvas 101
Basics
Few real examples
Scary Maths!
Conclusion
Slide 5
Slide 5 text
“Anyone can put paint
on a canvas, but only a
true master can bring
the painting to life”
Canvas 101
5
Shaun Jeffrey
Slide 6
Slide 6 text
● Top Left (0,0)
● Top Right (x,0)
● Bottom Left (0,y)
● Bottom Right (x,y)
Phone as Canvas
Slide 7
Slide 7 text
● Left ?
● Right ?
Phone as Canvas
Slide 8
Slide 8 text
● Left (0 , y/2)
● Right (x/2 , y/2)
Phone as Canvas
Slide 9
Slide 9 text
9
Canvas Basics
Slide 10
Slide 10 text
● Basic Canvas Setup in
Compose
@Composable
fun MyCanvasDemo() {
Canvas(
modifier = Modifier
.fillMaxSize()
.padding(4.dp)
) {
//draw shapes here
}
}
Setup Compose Canvas
Slide 11
Slide 11 text
11

DrawScope
Scoped drawing Environment
● drawLine - Draws line b’wn two given
points
● drawRect - Draws a rectangle
● drawImage - Draws a ImageBitmap
in Canvas
● drawRoundRect - Draws a rounded
border Rectangle
● drawCircle - Draws a circle with
given Radius
● drawArc - Draws an Arc with given
angles
● drawPath - Draws a path mapped by
us to give a custom UI
● drawPoint - Draws point with given
list of Offset.
Slide 12
Slide 12 text
12

Real life example!
Slide 13
Slide 13 text
● Let’s make this a reality!
Real Life example
Slide 14
Slide 14 text
● Let’s make this a reality!
Real Life example
Slide 15
Slide 15 text
● Structure code
@Composable
fun Seekbar(modifier:Modifier = Modifier){
Box(
modifier = modifier.size(200.dp)
.padding(16.dp)
) {
Canvas(modifier = Modifier.size(200.dp)) {
//arc and thumb drawing
}
}
}
Real life example
Slide 16
Slide 16 text
Canvas(modifier = Modifier.size(200.dp)) {
//arc and thumb drawing
}
Real life example
Slide 17
Slide 17 text
Canvas(modifier = Modifier.size(200.dp)) {
drawProgress(width = 10F)
drawThumb(size)
}
Real life example
Slide 18
Slide 18 text
Canvas(modifier = Modifier.size(200.dp)) {
drawProgress(width = 10F)
drawThumb(size)
}
Real life example
Slide 19
Slide 19 text
private fun DrawScope.drawThumb(size: Size) {
drawCircle(
color = Color.Blue, radius = 30F,
center = Offset(size.width.div(2),0F)
)
}
drawThumb
Slide 20
Slide 20 text
private fun DrawScope.drawThumb(size: Size) {
drawCircle(
color = Color.Blue, radius = 30F,
center = Offset(size.width.div(2),0F)
)
}
drawThumb
Slide 21
Slide 21 text
private fun DrawScope.drawThumb(size: Size) {
drawCircle(
color = Color.Blue, radius = 30F,
center = Offset(size.width.div(2),0F)
)
}
drawThumb
Slide 22
Slide 22 text
private fun DrawScope.drawThumb(size: Size) {
drawCircle(
color = Color.Blue, radius = 30F,
center = Offset(size.width.div(2),0F)
)
}
drawThumb
Slide 23
Slide 23 text
private fun DrawScope.drawThumb(size: Size) {
drawCircle(
color = Color.Blue, radius = 30F,
center = Offset(size.width.div(2),0F)
)
}
drawThumb
Slide 24
Slide 24 text
Coordinates mapping
drawThumb
Slide 25
Slide 25 text
private fun DrawScope.drawProgress(width: Float) {
drawArc(
color = Color.Black,
startAngle = 0.toFloat(),
sweepAngle = 360.toFloat(),
useCenter = false,
style = Stroke(
width = width,
cap = StrokeCap.Round
)
)}
drawProgress
Slide 26
Slide 26 text
private fun DrawScope.drawProgress(width: Float) {
drawArc(
color = Color.Black,
startAngle = 0.toFloat(),
sweepAngle = 360.toFloat(),
useCenter = false,
style = Stroke(
width = width,
cap = StrokeCap.Round
)
)}
drawProgress
Slide 27
Slide 27 text
private fun DrawScope.drawProgress(width: Float) {
drawArc(
color = Color.Black,
startAngle = 0.toFloat(),
sweepAngle = 360.toFloat(),
useCenter = false,
style = Stroke(
width = width,
cap = StrokeCap.Round
)
)}
drawProgress
Slide 28
Slide 28 text
private fun DrawScope.drawProgress(width: Float) {
drawArc(
color = Color.Black,
startAngle = 0.toFloat(),
sweepAngle = 360.toFloat(),
useCenter = false,
style = Stroke(
width = width,
cap = StrokeCap.Round
)
)}
drawProgress
Slide 29
Slide 29 text
private fun DrawScope.drawProgress(width: Float) {
drawArc(
color = Color.Black,
startAngle = 0.toFloat(),
sweepAngle = 360.toFloat(),
useCenter = false,
style = Stroke(
width = width,
cap = StrokeCap.Round
)
)}
drawProgress
Slide 30
Slide 30 text
private fun DrawScope.drawProgress(width: Float) {
drawArc(
color = Color.Black,
startAngle = 0.toFloat(),
sweepAngle = 360.toFloat(),
useCenter = false,
style = Stroke(
width = width,
cap = StrokeCap.Round
)
)}
drawProgress
Slide 31
Slide 31 text
private fun DrawScope.drawProgress(width: Float) {
drawArc(
color = Color.Black,
startAngle = 0.toFloat(),
sweepAngle = 360.toFloat(),
useCenter = false,
style = Stroke(
width = width,
cap = StrokeCap.Round
)
)}
drawProgress
Slide 32
Slide 32 text
private fun DrawScope.drawProgress(width: Float) {
drawArc(
color = Color.Black,
startAngle = 0.toFloat(),
sweepAngle = 360.toFloat(),
useCenter = false,
style = Stroke(
width = width,
cap = StrokeCap.Round
)
)}
drawProgress
detectDragGestures(
onDragStart = { // do something when drag starts },
onDragEnd = { // do something when drag ends },
onDrag = {pointerInputChange, offset->
// something more here!
val touchAngle = size.getAngle(pointerInputChange.position)
})
Scary Maths
Slide 54
Slide 54 text
fun getCenter(radius: Float, touchAngle: Float) = Offset(
x = ((radius) * cos(touchAngle)).plus(canvasCenter.x),
y = ((radius) * sin(touchAngle)).plus(canvasCenter.y))
Angle
Slide 55
Slide 55 text
fun getCenter(radius: Float, touchAngle: Float) = Offset(
x = ((radius) * cos(touchAngle)).plus(canvasCenter.x),
y = ((radius) * sin(touchAngle)).plus(canvasCenter.y))
Angle
Slide 56
Slide 56 text
fun getCenter(radius: Float, touchAngle: Float) = Offset(
x = ((radius) * cos(touchAngle)).plus(canvasCenter.x),
y = ((radius) * sin(touchAngle)).plus(canvasCenter.y))
Angle
Slide 57
Slide 57 text
fun getCenter(radius: Float, touchAngle: Float) = Offset(
x = ((radius) * cos(touchAngle)).plus(canvasCenter.x),
y = ((radius) * sin(touchAngle)).plus(canvasCenter.y))
Angle
Slide 58
Slide 58 text
Final Position
Angle
(x , y) = (r * cos(a), r* sin(a))
cos(a)
Sin(a)
Slide 59
Slide 59 text
59

Finally Dragged
Thumb!
Slide 60
Slide 60 text
private fun DrawScope.drawThumb(size: Size) {
drawCircle(
color = Color.Blue, radius = 30F,
center = Offset(size.width.div(2),0F)
)
}
drawThumb
Slide 61
Slide 61 text
61

But yes, it will be only
recomposed if its a
state!
Slide 62
Slide 62 text
62

Make it for all
screens!
Slide 63
Slide 63 text
● Use percentage and relative number.
● Don’t use hard coded values.
● Draw things with relation to size of canvas.
How can we make for all screens
Slide 64
Slide 64 text
64

Example of scaling
design!
Slide 65
Slide 65 text
fun DrawScope.drawThumb(size: Size) {
drawCircle(
color = Color.Blue, radius = 30F,
center = Offset(newX,newY)
)
}
drawThumb
Slide 66
Slide 66 text
fun DrawScope.drawThumb(size: Size) {
drawCircle(
color = Color.Blue, radius = 30F,
center = Offset(newX,newY)
)
}
drawThumb
hi_man_shoe
Slide 67
Slide 67 text
fun DrawScope.drawThumb(size: Size) {
val radius = size.width * (0.5).toFloat()
drawCircle(
color = Color.Blue, radius = radius,
center = Offset(newX,newY)
)
}
drawThumb
hi_man_shoe
Slide 68
Slide 68 text
fun DrawScope.drawThumb(size: Size) {
val radius = size.width * (0.5).toFloat()
drawCircle(
color = Color.Blue, radius = radius,
center = Offset(newX,newY)
)
}
drawThumb
hi_man_shoe
Slide 69
Slide 69 text
69

Testing Canvas
drawing!
Slide 70
Slide 70 text
● All drawings in one Canvas is one element.
● Testing it will be one Composable
● We can split it multiple components to make it different
composables
Testing Canvas Drawing
hi_man_shoe
Slide 71
Slide 71 text
71

Example!
Slide 72
Slide 72 text
Canvas(modifier = Modifier.size(200.dp)) {
drawProgress(width = 10F)
drawThumb(size)
}
Real life example
Slide 73
Slide 73 text
BoxWithConstraints(modifier = modifier.padding(Grid.Two)) {
val size = // Size from main canvas
Canvas(modifier = Modifier.testTag("testTag1")){}
Canvas(modifier = Modifier.testTag("testTag2")){}
}
Testing Canvas
hi_man_shoe
Slide 74
Slide 74 text
BoxWithConstraints(modifier = modifier.padding(Grid.Two)) {
val size = // Size from main canvas
Canvas(modifier = Modifier.testTag("testTag1")){}
Canvas(modifier = Modifier.testTag("testTag2")){}
}
Testing Canvas
hi_man_shoe
Slide 75
Slide 75 text
BoxWithConstraints(modifier = modifier.padding(Grid.Two)) {
val size = // Size from main canvas
Canvas(modifier = Modifier.testTag("testTag1")){}
Canvas(modifier = Modifier.testTag("testTag2")){}
}
Testing Canvas
hi_man_shoe
Slide 76
Slide 76 text
BoxWithConstraints(modifier = modifier.padding(Grid.Two)) {
val size = // Size from main canvas
Canvas(modifier = Modifier.testTag("testTag1")){}
Canvas(modifier = Modifier.testTag("testTag2")){}
}
Testing Canvas
hi_man_shoe
Slide 77
Slide 77 text
BoxWithConstraints(modifier = modifier.padding(Grid.Two)) {
val size = // Size from main canvas
Canvas(modifier = Modifier.testTag("testTag1")){}
Canvas(modifier = Modifier.testTag("testTag2")){}
}
Testing Canvas
hi_man_shoe
Slide 78
Slide 78 text
78

Miscellaneous.
Slide 79
Slide 79 text
● To Access inner canvas we use drawContext.canvas.nativeCanvas.
● Can be used for drawing Text.
Miscellaneous.