named above. Any other distribution, re-transmission, copying or disclosure of this message is strictly prohibited. If you have received this transmission in error, please notify me immediately by telephone or return email, and delete this presentation from your system. 360|AnDev 2018 Joshua Lamson @darkmoose117 Kotlinize Your Canvas
COURSE! • android.graphics.Canvas • An interface for the actual surface being drawn on (Bitmap) • Provided to you • View.onDraw(Canvas) • Drawable.onDraw(Canvas) • Canvas.draw*(…) • android.graphics.Paint • Dictates how things are drawn • Color, stoke style, shaders, etc • Also text attributes for TextPaint !3
Vals, init{} • and also constructors still but who cares about those • Extension Functions • Also named Parameters • Also defaults • Functional programming • Lambdas everywhere! !4
into discrete chunks • Axis / Grid • Percent Guidelines • Bars • What dimens are defined? • Padding outside of Axis • Spacing between bars • Bars themselves fill rest of width !5
init pattern, order matters • First Constructor Arguments (Child then Parent) • Parent Initializers in order (init {} and vals) • Parent Constructor • Child Initializers in order (init {} and vals) • Child Constructor • .apply { } makes for clean setup !17
without extending @ColorInt inline fun Context.getColorCompat(@ColorRes colorRes: Int) : Int { return ContextCompat.getColor(this, colorRes) } • Scoped into the object you’re extending • Similar to apply (this) • Android KTX • From Jake Wharton’s Google I/O 2018 Talk • Personal Opinion: • For your own code, GO NUTS !18
left: Float, right: Float, paint: Paint, heightForIndex: (Int) -> Float) { var y: Float for (i in 0 until numberOfGridLines) { y = heightForIndex(i) drawLine(left, y, right, y, paint) } }
left: Float, right: Float, paint: Paint, heightForIndex: (Int) -> Float) { var y: Float for (i in 0 until numberOfGridLines) { y = heightForIndex(i) drawLine(left, y, right, y, paint) } }
left: Float, right: Float, paint: Paint, heightForIndex: (Int) -> Float) { var y: Float for (i in 0 until numberOfGridLines) { y = heightForIndex(i) drawLine(left, y, right, y, paint) } }
left: Float, right: Float, paint: Paint, heightForIndex: (Int) -> Float) { var y: Float for (i in 0 until numberOfGridLines) { y = heightForIndex(i) drawLine(left, y, right, y, paint) } }
Array<T>, gridBounds: RectF, columnSpacing: Float = 0f, paint: Paint, fractionHeightForData: (T) -> Float) { val totalHorizontalSpacing = columnSpacing * (inputData.size + 1) val barWidth = (gridBounds.width() - totalHorizontalSpacing) / inputData.size var barLeft = gridBounds.left + columnSpacing var barRight = barLeft + barWidth for (datum in inputData) { // Figure out top of column based on INVERSE of percentage. Bigger the percentage, // the smaller top is, since 100% goes to 0. val top = gridBounds.top + gridBounds.height() * (1f - fractionHeightForData(datum)) drawRect(barLeft, top, barRight, grid.bottom, paint) // Shift over left/right column bounds barLeft += columnSpacing + barWidth barRight += columnSpacing + barWidth } }
Array<T>, gridBounds: RectF, columnSpacing: Float = 0f, paint: Paint, fractionHeightForData: (T) -> Float) { val totalHorizontalSpacing = columnSpacing * (inputData.size + 1) val barWidth = (gridBounds.width() - totalHorizontalSpacing) / inputData.size var barLeft = gridBounds.left + columnSpacing var barRight = barLeft + barWidth for (datum in inputData) { // Figure out top of column based on INVERSE of percentage. Bigger the percentage, // the smaller top is, since 100% goes to 0. val top = gridBounds.top + gridBounds.height() * (1f - fractionHeightForData(datum)) drawRect(barLeft, top, barRight, grid.bottom, paint) // Shift over left/right column bounds barLeft += columnSpacing + barWidth barRight += columnSpacing + barWidth } }
Array<T>, gridBounds: RectF, columnSpacing: Float = 0f, paint: Paint, fractionHeightForData: (T) -> Float) { val totalHorizontalSpacing = columnSpacing * (inputData.size + 1) val barWidth = (gridBounds.width() - totalHorizontalSpacing) / inputData.size var barLeft = gridBounds.left + columnSpacing var barRight = barLeft + barWidth for (datum in inputData) { // Figure out top of column based on INVERSE of percentage. Bigger the percentage, // the smaller top is, since 100% goes to 0. val top = gridBounds.top + gridBounds.height() * (1f - fractionHeightForData(datum)) drawRect(barLeft, top, barRight, grid.bottom, paint) // Shift over left/right column bounds barLeft += columnSpacing + barWidth barRight += columnSpacing + barWidth } }
Array<T>, gridBounds: RectF, columnSpacing: Float = 0f, paint: Paint, fractionHeightForData: (T) -> Float) { val totalHorizontalSpacing = columnSpacing * (inputData.size + 1) val barWidth = (gridBounds.width() - totalHorizontalSpacing) / inputData.size var barLeft = gridBounds.left + columnSpacing var barRight = barLeft + barWidth for (datum in inputData) { // Figure out top of column based on INVERSE of percentage. Bigger the percentage, // the smaller top is, since 100% goes to 0. val top = gridBounds.top + gridBounds.height() * (1f - fractionHeightForData(datum)) drawRect(barLeft, top, barRight, grid.bottom, paint) // Shift over left/right column bounds barLeft += columnSpacing + barWidth barRight += columnSpacing + barWidth } }
Array<T>, gridBounds: RectF, columnSpacing: Float = 0f, paint: Paint, fractionHeightForData: (T) -> Float) { val totalHorizontalSpacing = columnSpacing * (inputData.size + 1) val barWidth = (gridBounds.width() - totalHorizontalSpacing) / inputData.size var barLeft = gridBounds.left + columnSpacing var barRight = barLeft + barWidth for (datum in inputData) { // Figure out top of column based on INVERSE of percentage. Bigger the percentage, // the smaller top is, since 100% goes to 0. val top = gridBounds.top + gridBounds.height() * (1f - fractionHeightForData(datum)) drawRect(barLeft, top, barRight, grid.bottom, paint) // Shift over left/right column bounds barLeft += columnSpacing + barWidth barRight += columnSpacing + barWidth } }
to the extreme, you can write a Domain Specific Language (DSL) • When function is last argument can declare as block outside parens • Familiar from scoping functions • Very useful for wrapping logic in calls before/after it runs • canvas.save/restore() • Use inline to avoid object creation for Lambdas • Thanks Segun! !35
can’t provide an x/y to your Text Layouts or Paths, so you must move the canvas • Can do anything you could do to a Matrix • Rotate, Translate, Scale, Skew • Must always transform the Canvas back • canvas.save() & canvas.restore() • Can be saved multiple times & restored to certain save points • I think of Stamp/3D Printer. Base moved, paint stays stationary !36
Path that follows the user’s touch • “Wobbles” Heart as it’s shown • Rotation from -30° to 30° • While Canvas.drawShapes have positions, drawPath doesn’t • Transforms! !37
into elements to draw • Use reference points to position elements in your View • Setup Objects/Values needed up front for quick re-use • Keep your draw methods SLIM • 16ms is all you get • Avoid object allocations • Transform your Canvas to position text/paths • But ALWAYS save before and restore for others afterwards • INVALIDATE() to re-draw! !53
Joshua Lamson • https://bit.ly/2JPV68G • “An in-depth look at Kotlin’s initializers” by AJ Alt • https://bit.ly/2I73vyU • “Android Jetpack: sweetening Kotlin development with Android KTX” by Jake Wharton • https://youtu.be/st1XVfkDWqk • https://github.com/android/android-ktx • Kotlin Language Docs • https://kotlinlang.org/docs/reference/extensions.html • https://kotlinlang.org/docs/reference/lambdas.html • “Mastering Kotlin standard functions: run, with, let, also and apply” by Elye • https://bit.ly/2pfS00x !54 Bitbucket Project: https://bit.ly/2uBTaHf