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

Mobile Era 2019 - Tips for Building Custom View...

Mobile Era 2019 - 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.

Rebecca Franks

November 07, 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 madewithover.com
  2. 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
  3. 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
  4. 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) } }
  5. Fully Custom Component Manually draw the component on screen Handle

    your own Gestures/Touch events etc Difficulty Level: PRO
  6. 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 } }
  7. Canvas Based on top of SkCanvas - SKIA https://skia.org/ Most

    methods are “similar” on other platforms - ie Web has a <canvas> element
  8. 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 axis Y axis 0 y x width height
  9. 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
  10. Canvas#drawBitmap(…) Can be quite confusing & easy to stretch a

    bitmap - Make sure RectF is correct - Normalise your canvas values
  11. 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 }
  12. Paint Typeface information, shadowLayer information private val textPaint = Paint().apply

    { isAntiAlias = true textSize = fontSize letterSpacing = letterSpace typeface = newTypeface setShadowLayer(blurValue, x, y, Color.BLACK) }
  13. 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)
  14. 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
  15. 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
  16. 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.
  17. 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 } }
  18. 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]) }
  19. 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)
  20. 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) }
  21. 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 <include> 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
  22. Jetpack Compose • Declarative UI toolkit for Android • Inspired

    by React, Flutter, Vue… • Pre-alpha - ie not production ready • Unbundled from platform
  23. Canvas with Jetpack Compose @Composable fun DrawRectangle(color: Color) { val

    paint = Paint() paint.color = color Draw { canvas, parentSize -> canvas.drawRect(parentSize.toRect(), paint) } }
  24. 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…