Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Composing in your Canvas

Composing in your Canvas

Composing in your Canvas
With compose getting attraction and many apps moving towards it, it's time to see how we can draw and design complex yet beautiful UI using Canvas

In this talk,
1. We are going to see how to work with Basic out of box drawing for Canvas
2. How canvas works and is different from View system's canvas?
3. Draw a Custom UI with help for Compose's Canvas
4. Tips for better canvas drawings in Compose

I am telling you, let's make Mathematics fun to learn for developers!

Himanshu Singh

April 21, 2022
Tweet

More Decks by Himanshu Singh

Other Decks in Technology

Transcript

  1. • 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
  2. Talk Agenda 1. 2. 3. 4. 5. Canvas 101 Basics

    Few real examples Scary Maths! Conclusion
  3. “Anyone can put paint on a canvas, but only a

    true master can bring the painting to life” Canvas 101 5 Shaun Jeffrey
  4. • Top Left (0,0) • Top Right (x,0) • Bottom

    Left (0,y) • Bottom Right (x,y) Phone as Canvas
  5. • Basic Canvas Setup in Compose @Composable fun MyCanvasDemo() {

    Canvas( modifier = Modifier .fillMaxSize() .padding(4.dp) ) { //draw shapes here } } Setup Compose Canvas
  6. 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.
  7. • 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
  8. private fun DrawScope.drawThumb(size: Size) { drawCircle( color = Color.Blue, radius

    = 30F, center = Offset(size.width.div(2),0F) ) } drawThumb
  9. private fun DrawScope.drawThumb(size: Size) { drawCircle( color = Color.Blue, radius

    = 30F, center = Offset(size.width.div(2),0F) ) } drawThumb
  10. private fun DrawScope.drawThumb(size: Size) { drawCircle( color = Color.Blue, radius

    = 30F, center = Offset(size.width.div(2),0F) ) } drawThumb
  11. private fun DrawScope.drawThumb(size: Size) { drawCircle( color = Color.Blue, radius

    = 30F, center = Offset(size.width.div(2),0F) ) } drawThumb
  12. private fun DrawScope.drawThumb(size: Size) { drawCircle( color = Color.Blue, radius

    = 30F, center = Offset(size.width.div(2),0F) ) } drawThumb
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. Canvas(modifier = Modifier.size(200.dp) .pointerInput(true) { detectDragGestures( onDragStart = { //

    do something when drag starts }, onDragEnd = { // do something when drag ends } ){ } ) { //drawing } Scary Maths
  22. Canvas(modifier = Modifier.size(200.dp) .pointerInput(true) { detectDragGestures( onDragStart = { //

    do something when drag starts }, onDragEnd = { // do something when drag ends } ){ } ) { //drawing } Scary Maths
  23. Canvas(modifier = Modifier.size(200.dp) .pointerInput(true) { detectDragGestures( onDragStart = { //

    do something when drag starts }, onDragEnd = { // do something when drag ends }){ }) { //drawing } Scary Maths
  24. detectDragGestures( onDragStart = { // do something when drag starts

    }, onDragEnd = { // do something when drag ends }, onDrag = {pointerInputChange, offset-> val touchAngle = size.getAngle(pointerInputChange.position) }) Scary Maths
  25. detectDragGestures( onDragStart = { // do something when drag starts

    }, onDragEnd = { // do something when drag ends }, onDrag = {pointerInputChange, offset-> val touchAngle = size.getAngle(pointerInputChange.position) }) Scary Maths
  26. private fun IntSize.getAngle(offset: Offset) = ( atan2( y = center.x.minus(offset.x),

    x = center.y.minus(offset.y) ) * (180.toFloat().div(Math.PI.toFloat()))).toInt() Scary Maths
  27. private fun IntSize.getAngle(offset: Offset) = ( atan2( y = center.x.minus(offset.x),

    x = center.y.minus(offset.y) ) * (180.toFloat().div(Math.PI.toFloat()))).toInt() Scary Maths
  28. private fun IntSize.getAngle(offset: Offset) = ( atan2( y = center.x.minus(offset.x),

    x = center.y.minus(offset.y) ) * (180.toFloat().div(Math.PI.toFloat()))).toInt() Scary Maths
  29. private fun IntSize.getAngle(offset: Offset) = ( atan2( y = center.x.minus(offset.x),

    x = center.y.minus(offset.y) ) * (180.toFloat().div(Math.PI.toFloat()))).toInt() Scary Maths
  30. private fun IntSize.getAngle(offset: Offset) = ( atan2( y = center.x.minus(offset.x),

    x = center.y.minus(offset.y) ) * (180.toFloat().div(Math.PI.toFloat()))).toInt() Scary Maths
  31. 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
  32. fun getCenter(radius: Float, touchAngle: Float) = Offset( x = ((radius)

    * cos(touchAngle)).plus(canvasCenter.x), y = ((radius) * sin(touchAngle)).plus(canvasCenter.y)) Angle
  33. fun getCenter(radius: Float, touchAngle: Float) = Offset( x = ((radius)

    * cos(touchAngle)).plus(canvasCenter.x), y = ((radius) * sin(touchAngle)).plus(canvasCenter.y)) Angle
  34. fun getCenter(radius: Float, touchAngle: Float) = Offset( x = ((radius)

    * cos(touchAngle)).plus(canvasCenter.x), y = ((radius) * sin(touchAngle)).plus(canvasCenter.y)) Angle
  35. fun getCenter(radius: Float, touchAngle: Float) = Offset( x = ((radius)

    * cos(touchAngle)).plus(canvasCenter.x), y = ((radius) * sin(touchAngle)).plus(canvasCenter.y)) Angle
  36. private fun DrawScope.drawThumb(size: Size) { drawCircle( color = Color.Blue, radius

    = 30F, center = Offset(size.width.div(2),0F) ) } drawThumb
  37. • 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
  38. fun DrawScope.drawThumb(size: Size) { drawCircle( color = Color.Blue, radius =

    30F, center = Offset(newX,newY) ) } drawThumb hi_man_shoe
  39. 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
  40. 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
  41. • 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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