Custom Touchable Animated View in Kotlin

If you wanted to draw your own view, and having some animated drawing, and in Kotlin… hope this would help.

Multi-touch animated growing circle (rain drop like)

As show below is a view that I’ll be showing how to create in this writing.

It has

  1. Multi touch capability. Each touch will draw a circle
  2. The circle will grow in size and fade in color… and disappear

Making Custom View

1. Implementing the View

Firstly you’ll need to implement from the View class, as it is the fundamental UI component of Android.

class RainDropView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0) :
View(context, attrs, defStyleAttr, defStyleRes)

The nice part of Kotlin is, you could now have all the constructors combined into one with default constructor.

To have more attributes controlled through XML, you could refer to my other blog of making attributes.

2. Define onMeasure

This is to help you define your own height and width of the view. I provide a relatively basic model as below.

override fun onMeasure(
widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val desiredWidth = suggestedMinimumWidth +
paddingLeft + paddingRight
val desiredHeight = suggestedMinimumHeight +
paddingTop + paddingBottom
setMeasuredDimension(
resolveSize(desiredWidth, widthMeasureSpec),
resolveSize(desiredHeight, heightMeasureSpec))
}

To have better understanding of it you could read

3. Draw the View (and animate it)

Now go on to drawing, by using the onDraw function provided.

override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
rainDropList.forEach { it.draw(canvas, paint) }
rainDropList = rainDropList.filter { it.isValid() }
if (rainDropList.isNotEmpty() && isAttachedToWindow) {
invalidate()
}
}

Like normal java programming, we’re drawing it on the provided canvas.

I have a RainDrop class that encapsulate the drawing logic of drawing the circle and growing it.

rainDropList.forEach { it.draw(canvas, paint) }

Then I check if the rainDrop has reached its maximum size, I remove it, using

rainDropList = rainDropList.filter { it.isValid() }

Lastly, to make the drawing keep calling itself to redraw (or animate), we’ll call invalidate function. We’ll only need to do this if there’s still rainDrop to draw and the view is attached.

if (rainDropList.isNotEmpty() && isAttachedToWindow) {
invalidate()
}

4. Handle the touch

Finally, we now need to handle the touch

@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
val pointerIndex = event.actionIndex
when (event.actionMasked) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN -> return true
        MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> {
rainDropList += RainDrop(
event.getX(pointerIndex),
event.getY(pointerIndex), maxRadius)
invalidate()
return true
}
}
return super.onTouchEvent(event)
}

Do note, we’ll need to handle the ACTION_DOWN and ACTION_POINTER_DOWN (by returning true), else we’ll not be able to get the ACTION_UP and ACTION_POINTER_UP.

On ACTION_UP and ACTION_POINTER_UP (which is used for multi-touch), we create new RainDrop as per the detected XY position.

We call invalidate, to trigger the redrawing.

The code

Provided below is the code that does the work.

What’s next?

Having such animation looks cool, but imagine if you have complex logic of calculating the animation etc, you would like to offload it to some non UI treat processing, to avoid slowing down your user interaction. Check out the below…

I hope this post is helpful to you. You could check out my other interesting topics here.

Follow me on medium, Twitter or Facebook for little tips and learning on Android, Kotlin etc related topics. ~Elye~