1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
package ca.allanwang.kau.views
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import ca.allanwang.kau.utils.adjustAlpha
/**
* Created by Allan Wang on 2016-11-17.
*
*
* Canvas drawn ripples that keep the previous color
* Extends to view dimensions
* Supports multiple ripples from varying locations
*/
class RippleCanvas @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint: Paint = Paint()
private var baseColor = Color.TRANSPARENT
private val ripples: MutableList<Ripple> = mutableListOf()
init {
paint.isAntiAlias = true
paint.style = Paint.Style.FILL
}
/**
* Drawing the ripples involves having access to the next layer if it exists,
* and using its values to decide on the current color.
* If the next layer requests a fade, we will adjust the alpha of our current layer before drawing.
* Otherwise we will just draw the color as intended
*/
override fun onDraw(canvas: Canvas) {
val itr = ripples.listIterator()
if (!itr.hasNext()) return canvas.drawColor(baseColor)
var next = itr.next()
canvas.drawColor(colorToDraw(baseColor, next.fade, next.radius, next.maxRadius))
var last = false
while (!last) {
val current = next
if (itr.hasNext()) next = itr.next()
else last = true
//We may fade any layer except for the last one
paint.color = colorToDraw(current.color, next.fade && !last, next.radius, next.maxRadius)
canvas.drawCircle(current.x, current.y, current.radius, paint)
if (current.radius == current.maxRadius) {
if (!last) {
itr.previous()
itr.remove()
itr.next()
} else {
itr.remove()
}
baseColor = current.color
}
}
}
/**
* Given our current color and next layer's radius & max,
* we will decide on the alpha of our current layer
*/
internal fun colorToDraw(color: Int, fade: Boolean, current: Float, goal: Float): Int {
if (!fade || (current / goal <= FADE_PIVOT)) return color
val factor = (goal - current) / (goal - FADE_PIVOT * goal)
return color.adjustAlpha(factor)
}
fun ripple(color: Int, startX: Float = 0f, startY: Float = 0f, duration: Int = 1000, fade: Boolean = Color.alpha(color) != 255) {
val w = width.toFloat()
val h = height.toFloat()
val x = when (startX) {
MIDDLE -> w/2
END -> w
else -> startX
}
val y = when (startY) {
MIDDLE -> h/2
END -> h
else -> startY
}
val maxRadius = Math.hypot(Math.max(x, w - x).toDouble(), Math.max(y, h - y).toDouble()).toFloat()
val ripple = Ripple(color, x, y, 0f, maxRadius, fade)
ripples.add(ripple)
val animator = ValueAnimator.ofFloat(0f, maxRadius)
animator.duration = duration.toLong()
animator.addUpdateListener { animation ->
ripple.radius = animation.animatedValue as Float
invalidate()
}
animator.start()
}
fun set(color: Int) {
baseColor = color
ripples.clear()
invalidate()
}
internal class Ripple(val color: Int,
val x: Float,
val y: Float,
var radius: Float,
val maxRadius: Float,
val fade: Boolean)
companion object {
const val MIDDLE = -1.0f
const val END = -2.0f
const val FADE_PIVOT = 0.5f
}
}
|