3

I am currently creating an Android view in which, when the use tap it, I will display a sort of ripple around the coordinate of the tap.

But I'm not sure on how to do it. My first idea was to invalidate the cache and just make the circle bigger each time but it doesn't seem appropriate nor efficient to do this like that.

If anyone faced the same problem before and would love the share some tips on how to do it it would be much appreciated.

azizbekian
  • 60,783
  • 13
  • 169
  • 249
Matthieu Meunier
  • 458
  • 5
  • 21
  • 1
    You Can Cause Ripple effect on views or layouts by setting Selectable Item Background i.e as in xml "?android:attr/selectableItemBackground" and Programmatically its give in this solution https://stackoverflow.com/questions/37987732/programmatically-set-selectableitembackground-on-android-view also you may need it to set to clickable and focusable to true – nik Jan 28 '19 at 05:01
  • Hi, I don't think this solution will help me as this apply the ripple effect on the whole view (as it does on button and list items, etc) I would like to have a circle of +- 1cm diameter around the first finger to touch the view (which is basically a big colored rectangle) – Matthieu Meunier Jan 28 '19 at 14:12

2 Answers2

3

I finally found out a solution. Not a perfect one but it works for now.

Here is the code I did. Basically when I need it I change a boolean to true so my onDrawfunction knows that it have to execute the drawFingerPrintfunction.

The drawFingerPrint function, in the other end, just draw a circle that's bigger and bigger between each iteration until it reaches the diameter needed

private fun drawFingerPrint(canvas: Canvas) {
        canvas.drawCircle(pointerX, pointerY, radius, paint)

        if(radius<= 100F){
            radius+=10F
            invalidate()
        }
        else{
            radius = 0F
            drawAroundFinger = false
            invalidate()
        }
    }

I hope someone else will find this useful sometimes!

Matthieu

Matthieu Meunier
  • 458
  • 5
  • 21
1

As already mentioned @Matthieu, you need to draw circle on the canvas and invalidate the view. Here I provide a more complete example.
So we have an open class RippleView:

open class RippleView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : View(context, attrs) {

    private var rippleX: Float? = null
    private var rippleY: Float? = null
    private var rippleRadius: Float? = null
    var maxRippleRadius: Float = 100f // Retrieve from resources
    var rippleColor: Int = 0x88888888.toInt()

    private val ripplePaint = Paint().apply {
        color = rippleColor
    }

    private val animationExpand = object : Runnable {
        override fun run() {
            rippleRadius?.let { radius ->
                if (radius < maxRippleRadius) {
                    rippleRadius = radius + maxRippleRadius * 0.1f
                    invalidate()
                    postDelayed(this, 10L)
                }
            }
        }
    }

    private val animationFade = object : Runnable {
        override fun run() {
            ripplePaint.color.let { color ->
                if (color.alpha > 10) {
                    ripplePaint.color = color.adjustAlpha(0.9f)
                    invalidate()
                    postDelayed(this, 10L)
                } else {
                    rippleX = null
                    rippleY = null
                    rippleRadius = null
                    invalidate()
                }
            }

        }
    }

    fun startRipple(x: Float, y: Float) {
        rippleX = x
        rippleY = y
        rippleRadius = maxRippleRadius * 0.15f
        ripplePaint.color = rippleColor

        animationExpand.run()
    }

    fun stopRipple() {
        if (rippleRadius != null) {
            animationFade.run()
        }
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        val x = rippleX ?: return
        val y = rippleY ?: return
        val r = rippleRadius ?: return

        canvas.drawCircle(x, y, r, ripplePaint)
    }
}

fun Int.adjustAlpha(factor: Float): Int =
    (this.ushr(24) * factor).roundToInt() shl 24 or (0x00FFFFFF and this)

inline val Int.alpha: Int
    get() = (this shr 24) and 0xFF

Now you can extend any custom view with RippleView and use startRipple method when you get ACTION_DOWN, and stopRipplemethod when you get ACTION_UP:

class ExampleView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : RippleView(context, attrs), View.OnTouchListener {

    init {
        setOnTouchListener(this)
    }

    override fun onTouch(v: View, event: MotionEvent): Boolean {
        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
               if (isInsideArea(event.x, event.y)) {
                   startRipple(event.x, event.y)
               }
            }

            MotionEvent.ACTION_UP -> {
                stopRipple()
            }
        }

        return false
    }

    private fun isInsideArea(x: Float, y: Float): Boolean {
        TODO("Not yet implemented")
    }
}
Oleksandr Albul
  • 1,611
  • 1
  • 23
  • 31