The Android drawing and game APIs used throughout the book, with the chapter where each first appeared.
Paint — the "brush" (Ch1)
val paint = Paint()
paint.color = Color.RED
paint.style = Paint.Style.FILL // or Paint.Style.STROKE (outline)
paint.strokeWidth = 8f // line thickness when STROKE
paint.textSize = 70f // text height for drawText
paint.alpha = 128 // 0 (clear) to 255 (solid)
paint.isAntiAlias = true // smooth edges (good for shapes/text)
paint.isFilterBitmap = false // OFF = crisp pixel-art scaling (Ch22)
Color (Ch1, Ch22)
Color.RED, Color.WHITE, Color.BLACK, Color.CYAN, Color.DKGRAY // constants
Color.rgb(255, 140, 0) // red, green, blue (0..255)
Color.parseColor("#FF8800") // hex string
Canvas — the "paper" (Ch1)
canvas.drawColor(Color.DKGRAY) // fill whole screen
canvas.drawRect(left, top, right, bottom, paint) // rectangle
canvas.drawRoundRect(l, t, r, b, rx, ry, paint) // rounded corners
canvas.drawCircle(centerX, centerY, radius, paint) // circle
canvas.drawText("Score", x, y, paint) // text (y = baseline)
canvas.drawBitmap(bitmap, srcRect, dstRectF, paint) // image / sprite (Ch22)
// Temporary transforms, e.g. screen shake (Ch30):
canvas.save()
canvas.translate(dx, dy)
// ...draw...
canvas.restore()
Coordinates: (0, 0) is the top-left. X grows right, Y grows down.
Custom View + simple game loop (Ch1, Ch3)
class MyView(context: Context) : View(context) {
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// ...draw...
invalidate() // request another frame (the simple loop)
}
}
width and height give the view's size (Int). invalidate() asks for a redraw.
Touch input (Ch5)
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> { /* finger pressed */ }
MotionEvent.ACTION_MOVE -> { /* finger dragged */ }
}
val x = event.x // touch position (Float)
val y = event.y
return true // we handled it
}
Bitmaps & the texture atlas (Ch22)
// Load an image once, from the assets folder:
val texture = BitmapFactory.decodeStream(context.assets.open("lkbbag_texture.png"))!!
// Rect = source region in the texture (whole pixels)
val src = Rect(left, top, right, bottom)
// RectF = destination on screen (floats; can be scaled bigger)
val dst = RectF(x, y, x + w, y + h)
canvas.drawBitmap(texture, src, dst, paint)
Atlas file line format: name, x, y, width, height. Put the texture and atlas in app/src/main/assets/.
Rect vs RectF & collision (Ch22, Ch23)
Rect— integer rectangle (source regions in a texture).RectF— float rectangle (positions on screen, hit boxes).
RectF.intersects(boxA, boxB) // true if the two rectangles overlap
Audio — SoundPool (Ch22)
val soundPool = SoundPool.Builder().setMaxStreams(8).build()
val jump = soundPool.load(context, R.raw.sfx_jump, 1) // file in res/raw
soundPool.play(jump, 1f, 1f, 1, 0, 1f)
// id, leftVol, rightVol, priority, loop(0=once), rate(1f=normal pitch)
Vary the last value for pitch variation (Ch30). Sound files go in app/src/main/res/raw/.
Threaded game loop — SurfaceView (Ch20, Ch21)
class GameView(context: Context) : SurfaceView(context), Runnable {
private var thread: Thread? = null
@Volatile private var playing = false
private var deltaTime = 0f
override fun run() { // the loop, on its own thread
while (playing) {
val start = System.currentTimeMillis()
if (holder.surface.isValid) { update(); draw() }
val ms = System.currentTimeMillis() - start
if (ms < 16L) Thread.sleep(16L - ms) // ~60 FPS
deltaTime = (System.currentTimeMillis() - start) / 1000f
}
}
private fun draw() {
val canvas = holder.lockCanvas() // lock
// ...draw on canvas...
holder.unlockCanvasAndPost(canvas) // post to screen
}
fun resume() { playing = true; thread = Thread(this); thread?.start() }
fun pause() { playing = false; thread?.join() }
}
Start/stop from the activity's onResume() / onPause().
Delta-time movement (Ch21)
// Speed is pixels PER SECOND; multiply by delta so motion is frame-rate independent
x += velX * deltaSeconds
Saving data — SharedPreferences (Ch30)
val prefs = context.getSharedPreferences("mygame", Context.MODE_PRIVATE)
val best = prefs.getInt("highScore", 0) // read (default 0)
prefs.edit().putInt("highScore", best).apply() // write