$30 off During Our Annual Pro Sale. View Details »

Tips for Building Custom Views on Android with Canvas APIs

Tips for Building Custom Views on Android with Canvas APIs

Have you ever wanted to draw something custom beyond the standard views like a Bar Chart or an Advanced Image Viewer? In this talk, we will cover the basics of drawing onto a Canvas to create your own custom view. We will also cover some of the more advanced things you can do with the Canvas, such as using Shaders and Matrices to achieve magical effects.

Video: https://skillsmatter.com/skillscasts/13109-tips-for-building-custom-views-on-android-with-canvas-apis

Rebecca Franks

June 19, 2019
Tweet

More Decks by Rebecca Franks

Other Decks in Programming

Transcript


  1. Tips for Building Custom Views
    on Android with Canvas APIs

    @riggaroo
    Rebecca Franks

    View Slide

  2. Hi
    Android Developer @ Over
    Google Developer Expert in Android
    @riggaroo
    riggaroo.co.za
    I’m Rebecca Franks
    %

    View Slide


  3. What is a Custom View?

    View Slide

  4. What?
    UI Component not part of the “stock standard”
    controls.
    When?
    Standard Android Framework Views don’t have what
    you need
    Want a grouping of views for easier use

    View Slide

  5. Compound View (Group
    of Views)
    Extend a ViewGroup (ie LinearLayout,
    ConstraintLayout etc)
    Override one or two things / group a
    bunch of existing components together
    Difficulty Level: Intermediate

    View Slide

  6. Compound View
    class CompoundView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
    ) : LinearLayout(context, attrs, defStyleAttr) {
    init {
    inflate(context, R.layout.compound_view_example, this)
    }
    }

    View Slide

  7. Fully Custom
    Component
    Manually draw the component on
    screen
    Handle your own Gestures/Touch
    events etc
    Difficulty Level: PRO

    View Slide

  8. Fully Custom Component
    class CustomView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
    ) : View(context, attrs, defStyleAttr) {
    // Called when the view should render its content.
    override fun onDraw(canvas: Canvas?) {
    super.onDraw(canvas)
    // DRAW STUFF HERE
    }
    }

    View Slide


  9. What is a Canvas?

    View Slide

  10. Blank Space
    to draw onto

    View Slide

  11. Canvas
    Based on top of SkCanvas - SKIA https://skia.org/
    Most methods are “similar” on other platforms
    - ie Web has a element

    View Slide

  12. Canvas - Co-ordinate System
    Top left is [0,0]- the starting point of that
    Canvas.
    All elements are placed relative to [0,0]
    Uses pixel values - not dp
    New Canvas commands draw over previously
    drawn items
    0 X
    Y
    0
    y
    x
    width
    height

    View Slide

  13. How do I use a Canvas?
    You need 4 things:
    1. Bitmap - to hold the pixels
    2. Canvas - host drawing calls
    3. Draw Commands - describe what to draw
    4. Paint - describe how to draw

    View Slide

  14. What can I draw on a
    Canvas?

    View Slide

  15. Canvas#drawBitmap(…)
    canvas.drawBitmap(bitmap, null, destRect, paint)

    View Slide

  16. Canvas#drawBitmap(…)
    Can be quite confusing & easy to stretch a
    bitmap
    - Make sure RectF is correct
    - Normalise your canvas values

    View Slide

  17. Other Canvas APIs
    drawRect(pixelPointRect, whitePaint)
    drawCircle(x, y, SIZE_MAGNIFIER, paint)

    View Slide

  18. Paint

    View Slide

  19. Paint
    Holds style and colour information for drawing text,
    bitmaps, paths etc.
    private val textPaint =
    Paint().apply {
    isAntiAlias = true
    color = Color.RED
    style = Paint.Style.STROKE
    }

    View Slide

  20. Paint
    Typeface information, shadowLayer information
    private val textPaint =
    Paint().apply {
    isAntiAlias = true
    textSize = fontSize
    letterSpacing = letterSpace
    this.typeface = newTypeface
    setShadowLayer(blurValue, x, y, Color.BLACK)
    }

    View Slide

  21. Paint with BitmapShader

    View Slide

  22. Paint with BitmapShader
    Draw parts of a bitmap
    val bitmapShader = BitmapShader(backingBitmap, TileMode.CLAMP, TileMode.CLAMP)
    val dropperPaint = Paint().apply {
    shader = bitmapShader
    }
    drawingMatrix.postScale(SCALE, SCALE, point.x, point.y)
    dropperPaint.shader?.setLocalMatrix(drawingMatrix)
    //.. in onDraw
    drawCircle(point.x, point.y, SIZE_OF_MAGNIFIER, dropperPaint)

    View Slide

  23. View Slide

  24. Tip:
    Use KTX for cleaner code

    View Slide

  25. implementation "androidx.core:core-ktx:1.1.0-alpha05"
    Core KTX is a set of common extensions provided by
    the Android Team at Google.
    What is KTX?

    View Slide

  26. canvas.save()
    canvas.translate(200f, 300f)
    canvas.drawCircle(...) // drawn on the translated canvas
    canvas.restore()
    Without KTX

    View Slide

  27. canvas.withTranslate(200f, 300f) {
    drawCircle(...)
    }
    With KTX

    View Slide

  28. val translateCheckpoint = canvas.save()
    canvas.translate(200f, 300f)
    canvas.drawCircle(150f, 150f, RADIUS, circlePaint)
    val rotateCheckpoint = canvas.save()
    canvas.rotate(45f)
    canvas.drawRect(rect, rectPaint)
    canvas.restoreToCount(rotateCheckpoint)
    canvas.restoreToCount(translateCheckpoint)
    Without KTX

    View Slide

  29. canvas.withTranslation(200f, 300f) {
    drawCircle(150f, 150f, RADIUS, circlePaint)
    withRotation(45f) {
    drawRect(rect, rectPaint)
    }
    }
    With KTX

    View Slide

  30. Tip:
    Use Matrices to make Canvas
    Operations Easier

    View Slide

  31. Matrix
    3*3 matrix for transforming canvas or co-ordinates
    - Scale, Skew, Rotate, Translate, Perspective

    View Slide

  32. Matrix
    val newMatrix = Matrix()
    // onDraw…
    newMatrix.postRotate(20f)
    canvas.withMatrix(newMatrix) {
    drawBitmap(bitmap, null, rect, paint)
    }

    View Slide

  33. Perspective drawing
    matrix.setPolyToPoly(src, 0, dst, 0, points)
    canvas.withMatrix(newMatrix) {
    drawBitmap(bitmap, null, rect, paint)
    }

    View Slide

  34. val src = floatArrayOf(
    0f, 0f, // top left point
    width, 0f, // top right point
    width, height, // bottom right point
    0f, height // bottom left point
    )
    val dst = floatArrayOf(
    50f, -200f, // top left point
    width, -200f, // top right point
    width, height +200f, // bottom right point
    0f, height // bottom left point
    )
    Perspective drawing

    View Slide

  35. Tip:
    How to work with two
    Coordinate Systems

    View Slide

  36. Working with sizes and points is difficult
    Measured Width of View = 1080
    Measured Height of View = 2180
    My data class sizes are in the range:
    0 - 100 or 0 to Custom Size
    All points inside are also in that
    range.

    View Slide

  37. Scale/Translate Canvas early on in rendering
    Measured Width of View = 1080
    Measured Height of View = 2180

    val (scale, x, y) = size.fitCenter(canvas.size())
    canvas.withTranslation(x, y) {
    withScale(scale, scale) {
    // Canvas at this point will now
    // be in your coordinate system
    }
    }

    View Slide

  38. Mapping points between two co-ordinate
    systems
    fun mapPoint(point: Point): Point {
    computeMatrix.reset()
    computeMatrix.postTranslate(20f, 20f)
    computeMatrix.postRotate(20f, x, y)
    val arrayPoint = floatArrayOf(point.x, point.y)
    computeMatrix.mapPoints(arrayPoint)
    return Point(arrayPoint[0], arrayPoint[1])
    }

    View Slide

  39. Tip:
    Check that the Canvas APIs you are
    using work across different API versions

    View Slide

  40. Paint#setShadowLayer
    Works on drawText()
    Weird results with drawBitmap() and hardware acceleration
    Doesn’t work 100% with drawPath()
    paint.setShadowLayer(20f, 200f, 200f, Color.BLACK)

    View Slide

  41. Paint#setShadowLayer
    API 26
    (Hardware acc)
    API 28 - Hardware acc
    API 26 - Software acc

    View Slide

  42. Difference in Hardware / Software Rendering
    Creating a Canvas offscreen - always software rendered
    On screen Canvas - choose hardware or software
    To disable HA on a view (you probably don’t want to do this):
    init {
    setLayerType(LAYER_TYPE_SOFTWARE,null)
    }

    View Slide

  43. https://developer.android.com/guide/topics/graphics/hardware-accel.html
    Supported hardware methods from API level

    View Slide

  44. Test your Canvas code on
    all different API levels ✅

    View Slide

  45. When should you use a Custom
    View / Fragment / ?

    View Slide

  46. Fragment
    Whole logical component in
    your app that is mostly
    standalone
    Don’t need custom drawing
    Using Built in components
    Don’t really need callbacks
    to other parts (workarounds
    with ViewModels but not
    great)
    When built in components
    don’t cut it - ie need a
    Canvas drawing, Custom
    Gesture Handling
    Encapsulate logic and
    expose a callback listener to
    user of a component
    Don’t want users to be
    concerned of the internals
    Custom View
    Literally just bunch of
    layouts grouped in a file
    Need each individual
    component available in
    layout
    No custom logic around
    touches / drawing etc
    Manually handle each
    components logic yourself

    View Slide


  47. Looking to the Future:
    Jetpack Compose?

    View Slide

  48. Jetpack Compose
    • Declarative UI toolkit for Android
    • Inspired by React, Flutter, Vue…
    • Pre-alpha - ie not production ready
    • Unbundled from platform

    View Slide

  49. Jetpack Compose
    import androidx.compose.*
    import androidx.ui.core.*
    @Composable
    fun Greeting(name: String) {
    Text ("Hello $name!")
    }

    View Slide

  50. Canvas with Jetpack Compose
    @Composable
    fun DrawRectangle(color: Color) {
    val paint = Paint()
    paint.color = color
    Draw { canvas, parentSize ->
    canvas.drawRect(parentSize.toRect(), paint)
    }
    }

    View Slide

  51. Final Thoughts
    - Normalise your canvas coordinate system
    - Use Matrix class for easier transformations
    - Double check that the Canvas features are available
    - Use KTX
    - Canvas concepts are not disappearing anytime soon…

    View Slide

  52. Thank you!
    Rebecca Franks
    [email protected]
    @riggaroo

    View Slide