Любой Android разработчик работал с кнопками, поэтому видел ripple эффект и всю его красоту.
Иногда хочется реализовать что-нибудь кастомное нежели стандартные вещи, которые уже предоставляются компонентами Material Design.
Поэтому я решил написать наследник AppCompatImageView
и сделать для него свой ripple эффект с минимальным количеством кода.
Здесь вы можете посмотреть как это выглядит.
Сразу выкладываю код:
private class RippleAnimator(private var radius: Float, private var delay: Long) {
private val animators = mutableListOf<ValueAnimator>()
private var isRunning: Boolean = false
fun changeRippleRadius(radius: Float) {
this.radius = radius
}
fun changeDelay(delay: Long) {
this.delay = delay
}
fun start(updateListener: ValueAnimator.AnimatorUpdateListener) {
val anim = ValueAnimator.ofFloat(0f, radius)
anim.addUpdateListener(updateListener)
anim.doOnStart { isRunning = true }
anim.doOnEnd { isRunning = false }
anim.duration = delay
animators.firstOrNull()?.cancel()
animators.clear()
animators.add(anim)
anim.start()
}
fun stop(after: () -> Unit) {
if (isRunning) {
animators.firstOrNull()?.doOnEnd {
after()
}
} else {
after()
}
}
}
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 var pointX = 0f
private var pointY = 0f
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val rippleAnimator = RippleAnimator(0f, 300L)
init {
isClickable = true
changeRippleColor(Color.GREEN)
}
fun changeRippleDuration(duration: Long) {
rippleAnimator.changeDelay(duration)
}
fun changeRippleColor(color: Int) {
paint.color = Color.argb(90, Color.red(color), Color.green(color), Color.blue(color))
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
rippleAnimator.changeRippleRadius(w * 2f)
radius = w * 2f
}
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
pointX = event.x
pointY = event.y
rippleAnimator.start {
initial = it.animatedValue as Float
invalidate()
}
}
MotionEvent.ACTION_UP -> {
rippleAnimator.stop {
initial = 0f
invalidate()
}
}
}
return true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawCircle(pointX, pointY, initial, paint)
}
}
Прошу прощения, что без пояснений, добавлю если кто-нибудь не разберется :)
Основная проблема заключается в создании маленькой длительности и возможности отменять предыдущий ripple эффект при повторном нажатии.
RippleImageButton
далеко не является идеальным и не соотвествует Material Design стандартам, я лишь хотел показать возможность такого варианта.
Всем хорошего кода :)
Комментарии (11)
shaman4d
13.09.2021 22:42+1Ну скрикаст хоть бы записали - любопытно сначала на результат ведь посмотреть!
vzhilin
14.09.2021 00:03+2Пара вопросов:
Вас не смущает горячий цикл в Delay?
Почему в Delay задержка всего 30 микросекунд? C какой частотой кадров будет проигрываться ваша анимация?
KiberneticWorm Автор
14.09.2021 15:51Я вынес анимацию на другой поток, а обновление UI осуществляется только на главном
KiberneticWorm Автор
14.09.2021 16:0130 миллисекунд я подобрал экспериметнально, задержка осуществляется между увеличением шага радиуса ripple эффекта.
vzhilin
14.09.2021 16:33+1val interval: Long = 30000 val start = System.nanoTime() var end: Long = 0 do { end = System.nanoTime() } while (start + interval >= end)
Нет, ну смотрите: 1 наносекунда это 10e-9 секунд. 30 000 * 10e-9 получаем 30 * 10e-6. Это 30 микросекунд.
vzhilin
14.09.2021 16:37+1do { end = System.nanoTime() } while (start + interval >= end)
Хорошо, но другой поток разве не будет непрерывано вызывать метод
System.nanoTime()
, эффективно превращая заряд батареи в тепло?lorc
14.09.2021 21:20А потом говорят что андроид тормозит и жрет батарею...
KiberneticWorm Автор
15.09.2021 15:17+1Да, я ошибся 30 микросекунд, что касается другого потока, я думаю можно найти решение получше, нежели тупо использовать тяжелый поток для какого то простого эффекта.
KiberneticWorm Автор
16.09.2021 15:51Я поменял код, сейчас стало гораздо понятнее, можно менять цвет и задержку Ripple эффекта
kazinaki
15.09.2021 15:15А чем вас не устраивает
"?attr/selectableItemBackground"
и
"?attr/selectableItemBackgroundBorderless"
?
etozhesano
Не хватает примера для наглядности. Мне например впадлу создавать нулевой проект для проверки