Простой Ripple эффект своими руками для Android

Kate

Administrator
Команда форума
Любой Android разработчик работал с кнопками, поэтому видел ripple эффект и всю его красоту.

Иногда хочется реализовать что-нибудь кастомное нежели стандартные вещи, которые уже предоставляются компонентами Material Design.

Поэтому я решил написать наследник AppCompatImageView и сделать для него свой ripple эффект с минимальным количеством кода.

Сразу выкладываю код:

private class Watcher {
private val observers = mutableListOf<() -> Unit>()

fun tellAbout() = observers.forEach { it.invoke() }
fun replace(observer: () -> Unit) {
if (observers.isNotEmpty()) {
observers.clear()
}
observers.add(observer)
}
}

private class Delay {
fun waitForSomeInterval() {
val interval: Long = 30000
val start = System.nanoTime()
var end: Long = 0
do {
end = System.nanoTime()
} while (start + interval >= end)

}
}

class RippleImageButton @JvmOverloads constructor(
ctx: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatImageView(ctx, attrs, defStyleAttr) {

private var radius: Float = 0f
private var initial: Float = 0f
private val step = 0.09f

private var pointX = 0f
private var pointY = 0f

private var rippleColor = Color.argb(60, 33, 33, 150)

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


init {
isClickable = true
}

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
radius = w * 2f
}

private val watcher = Watcher()
private var isRunning = false

private val uiHandler = Handler(Looper.getMainLooper())
private val threadPool = Executors.newSingleThreadExecutor()
private val submits = mutableListOf<Future<*>>()

private val rippleRunnable = Runnable {
initial = 0f
if (isRunning) {
return@Runnable
}
isRunning = true
while (isRunning) {
initial += step
if (initial > radius) {
break
}
uiHandler.post { invalidate() }
Delay().waitForSomeInterval()
}
uiHandler.post {
isRunning = false
watcher.tellAbout()
invalidate()
}
}

private fun stopLast() {
isRunning = false
submits.forEach { it.cancel(true) }
}


override fun onTouchEvent(event: MotionEvent): Boolean {

when (event.action) {
MotionEvent.ACTION_DOWN -> {
pointX = event.x
pointY = event.y

watcher.replace { }
stopLast()

uiHandler.postDelayed({
submits.add(threadPool.submit(rippleRunnable))
}, 1L)

}
MotionEvent.ACTION_UP -> {
if (isRunning) {
watcher.replace {
initial = 0f
invalidate()
}
} else {
initial = 0f
invalidate()
}
}
}
return true
}

override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawCircle(pointX, pointY, initial, paint)

}

}
Прошу прощения, что без пояснений, добавлю если кто-нибудь не разберется :)

Основная проблема заключается в создании маленькой длительности и возможности отменять предыдущий ripple эффект при повторном нажатии.

RippleImageButton далеко не является идеальным и не соотвествует Material Design стандартам, я лишь хотел показать возможность такого варианта.

Всем хорошего кода :)

 
Сверху